Your getKeyPair() generates a new key pair each time it is executed, which is referenced with alias (i.e. the previous key pair gets lost).
Since your getPublic() calls getKeyPair() internally, a new key pair is generated when it is called. This design increases the risk of unrelated private and public keys, which could be a possible cause of the problem.
Example: If getKeyPair() is called first, then the key agreement is generated with the private key (and the public key of the other side) and finally the public key is exported with getPublic(), a new key pair is generated in the last step, so that the private key used for the key agreement is not related to the exported public key for the other side so that a different key agreement is generated on the other side.
Whether this or something similar applies cannot be said with certainty, since it is not clear from your post in which order key generation, generation of the key agreement and export of the public key take place.
But to avoid such problems you have to make sure related keys are used. To do this, the key generation should be removed from getPublicKey(). The public key should be determined in getPublicKey() from the AndroidKeyStore using the alias (as in the following example).
Sample implementation (both sides Android):
// create A-side keys and export public key
val privateKeyA = getKeyPair("a-side")?.private
val exportedPublicKeyA = getPublicKey("a-side")
// create B-side keys and export public key
val privateKeyB = getKeyPair("b-side")?.private
val exportedPublicKeyB = getPublicKey("b-side")
// import pubic B-side key and create A-side key agreement
val keyFactory = KeyFactory.getInstance("EC")
val publicKeyB = keyFactory.generatePublic(X509EncodedKeySpec(Base64.decode(exportedPublicKeyB, Base64.NO_WRAP)));
println(Base64.encodeToString(getKeyAgreement(privateKeyA, publicKeyB), Base64.NO_WRAP));
// import pubic A-side key and create B-side key agreement
val publicKeyA = keyFactory.generatePublic(X509EncodedKeySpec(Base64.decode(exportedPublicKeyA, Base64.NO_WRAP)));
println(Base64.encodeToString(getKeyAgreement(privateKeyB, publicKeyA), Base64.NO_WRAP));
In this use case, getPublicKey() reads the public key from the AndroidKeyStore using the alias for which there must be a key pair. getKeyPair() is essentially unchanged (only the alias is additionally passed). getKeyAgreement() calculates the key agreement (to demonstrate that both sides do indeed generate the same key agreement).
fun getPublicKey(alias: String): String? {
val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
load(null)
}
val entry = ks.getEntry(alias, null) as? KeyStore.PrivateKeyEntry
val key = entry?.certificate?.publicKey;
return Base64.encodeToString(key?.encoded, Base64.NO_WRAP)
}
fun getKeyPair(alias: String): KeyPair? {
val keyPair: KeyPair?
val keyPairGenerator: KeyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC,
"AndroidKeyStore"
)
keyPairGenerator.initialize(
KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_AGREE_KEY)
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
.build()
)
keyPair = keyPairGenerator.generateKeyPair()
return keyPair
}
fun getKeyAgreement(priv1: PrivateKey?, pub2: PublicKey?): ByteArray {
val ka = KeyAgreement.getInstance("ECDH")
ka.init(priv1)
ka.doPhase(pub2, true)
return ka.generateSecret("ECDH").encoded
}
getPublicKey()creates a new key pair (asalias). If a key pair was previously generated, this will be overwritten, so that the private and public keys may not be associated.MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZ5DrInTCSTjtozTfIhLXIl5FARD+the3I4rqa+NiQzXttruqhEoWwsd+xl9HG09sVwD7Sc1ckwoTmsUPsFeHzQ==