package app.fedilab.android.helper; import android.content.Context; import android.content.SharedPreferences; import android.util.Base64; import android.util.Log; import androidx.preference.PreferenceManager; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Security; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.crypto.KeyAgreement; import app.fedilab.android.client.Entities.Account; // https://github.com/nelenkov/ecdh-kx/blob/master/src/org/nick/ecdhkx/Crypto.java public class ECDH { private static final String kp_public = "kp_public"; private static final String kp_private = "kp_private"; private static final String peer_public = "peer_public"; private static final String TAG = ECDH.class.getSimpleName(); private static final String PROVIDER = "SC"; private static final String KEGEN_ALG = "ECDH"; private static ECDH instance; static { Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1); } private final KeyFactory kf; private final KeyPairGenerator kpg; public ECDH() { try { kf = KeyFactory.getInstance(KEGEN_ALG, PROVIDER); kpg = KeyPairGenerator.getInstance(KEGEN_ALG, PROVIDER); } catch (NoSuchAlgorithmException | NoSuchProviderException e) { throw new RuntimeException(e); } } public static synchronized ECDH getInstance() { if (instance == null) { instance = new ECDH(); } return instance; } static String base64Encode(byte[] b) { return Base64.encodeToString(b, Base64.DEFAULT); } static byte[] base64Decode(String str) { return Base64.decode(str, Base64.DEFAULT); } synchronized KeyPair generateKeyPair() throws Exception { ECGenParameterSpec ecParamSpec = new ECGenParameterSpec("secp256k1"); kpg.initialize(ecParamSpec); return kpg.generateKeyPair(); } byte[] generateSecret(PrivateKey myPrivKey, PublicKey otherPubKey) throws Exception { ECPublicKey ecPubKey = (ECPublicKey) otherPubKey; Log.d(TAG, "public key Wx: " + ecPubKey.getW().getAffineX().toString(16)); Log.d(TAG, "public key Wy: " + ecPubKey.getW().getAffineY().toString(16)); KeyAgreement keyAgreement = KeyAgreement.getInstance(KEGEN_ALG, PROVIDER); keyAgreement.init(myPrivKey); keyAgreement.doPhase(otherPubKey, true); return keyAgreement.generateSecret(); } synchronized PublicKey readPublicKey(String keyStr) throws Exception { X509EncodedKeySpec x509ks = new X509EncodedKeySpec( base64Decode(keyStr)); return kf.generatePublic(x509ks); } synchronized PrivateKey readPrivateKey(String keyStr) throws Exception { PKCS8EncodedKeySpec p8ks = new PKCS8EncodedKeySpec( base64Decode(keyStr)); return kf.generatePrivate(p8ks); } synchronized KeyPair readKeyPair(String pubKeyStr, String privKeyStr) throws Exception { return new KeyPair(readPublicKey(pubKeyStr), readPrivateKey(privKeyStr)); } KeyPair newPair(Context context, Account account) { SharedPreferences.Editor prefsEditor = PreferenceManager .getDefaultSharedPreferences(context).edit(); KeyPair kp = null; try { kp = generateKeyPair(); } catch (Exception e) { e.printStackTrace(); return null; } prefsEditor.putString(kp_public + account.getId() + account.getInstance(), base64Encode(kp.getPublic().getEncoded())); prefsEditor.putString(kp_private + account.getId() + account.getInstance(), base64Encode(kp.getPrivate().getEncoded())); prefsEditor.commit(); return kp; } synchronized KeyPair getPair(Context context, Account account) { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context); String strPub = prefs.getString(kp_public + account.getId() + account.getInstance(), ""); String strPriv = prefs.getString(kp_private + account.getId() + account.getInstance(), ""); if (strPub.isEmpty() || strPriv.isEmpty()) { return newPair(context, account); } try { return readKeyPair(strPub, strPriv); } catch (Exception e) { e.printStackTrace(); } return null; } public String getPublicKey(Context context, Account account) { return base64Encode(getPair(context, account).getPublic().getEncoded()); } void saveServerKey(Context context, String strPeerPublic) { SharedPreferences.Editor prefsEditor = PreferenceManager .getDefaultSharedPreferences(context).edit(); prefsEditor.putString(peer_public, strPeerPublic); prefsEditor.commit(); } PublicKey getServerKey(Context context) throws Exception { return readPublicKey( PreferenceManager.getDefaultSharedPreferences(context) .getString(peer_public, "") ); } byte[] getSecret(Context context, Account account) { try { return generateSecret(getPair(context, account).getPrivate(), getServerKey(context)); } catch (Exception e) { e.printStackTrace(); return null; } } }