I'm generating 2048-bit RSA public and private keys, encoding them in X509 format, converting the result to PKCS#1 format, encoding that as a Base64 string and saving it in a database. Then I retrieve the string from the database, restore it to PKCS#1 format, convert it to X509 format, and then restore the public key object.
When I call println() on the original and the restored public key objects, I confirm that they have the same modulus and public exponent. When I check their equality with .equals(), the assertion returns true. However, when I compare the X509 encoded representation of the original public key object and that of the restored public key object, they are different. This is problematic for me because I use a hash derived from this value in order to identify the keys.
Why is this happening, and how can I fix it?
I must store keys in the database in PKCS#1 format because the database file will be moved to an iOS device the keys will be restored there, and Apple's Security framework only allows the export or import of RSA keys in PKCS#1 format.
I'm using Kotlin on JVM; the code below reproduces the issue:
import org.bouncycastle.asn1.ASN1Primitive
import org.bouncycastle.asn1.DERNull
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import java.security.KeyFactory
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.PublicKey
import java.security.spec.X509EncodedKeySpec
import java.util.*
import kotlin.test.Test
class IssueExample {
@Test
fun demonstrateProblem() {
//Generate RSA key pair
val keyPairGenerator: KeyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(2048)
val keyPair = keyPairGenerator.generateKeyPair()
//Convert public key to X509, then to PKCS1 format
val pubKeyX509Bytes = keyPair.public.encoded
val sPubKeyInfo: SubjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(pubKeyX509Bytes)
val pubKeyPrimitive: ASN1Primitive = sPubKeyInfo.parsePublicKey()
val pubKeyPkcs1Bytes: ByteArray = pubKeyPrimitive.encoded
//Convert PKCS1 to Base64 string
val encoder = Base64.getEncoder()
val base64PubKey = encoder.encodeToString(pubKeyX509Bytes)
//Restore Base64 string to PKCS#1
val decoder = Base64.getDecoder()
val restoredPubKeyPKCS1Bytes = decoder.decode(base64PubKey)
//Restore PKCS#1 to X509
val algorithmIdentifier: AlgorithmIdentifier = AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE);
val restoredPubKeyX509Bytes: ByteArray = SubjectPublicKeyInfo(algorithmIdentifier, pubKeyPkcs1Bytes).encoded
//Restore X509 tp public key
val restoredPubKey: PublicKey = KeyFactory.getInstance("RSA").generatePublic(X509EncodedKeySpec(pubKeyX509Bytes))
//Comparing the two public key objects returns true
println(restoredPubKey.equals(keyPair.public))
//And calling println() yields identical results for both
println(restoredPubKey)
println(keyPair.public)
//Yet their X509 representations return false!!
println(restoredPubKey.encoded.equals(keyPair.public.encoded))
println(restoredPubKey.encoded)
println(keyPair.public.encoded)
}
}