0

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)
    }
}
jimmyt
  • 11
  • 1
  • Try dumping the encodings with e.g. OpenSSL asn1parse; with a little luck that will show if there are structural differences – clausc Nov 29 '21 at 09:02
  • I'm not an expert in Kotlin but in Java `.equals` on arrays is not what you think it is. For that matter neither is `.toString()` or whatever it's called in Kotlin, so the last two lines don't print anything meaningful. See https://stackoverflow.com/q/12292513/238704. Replace the 3rd line from the botton with `println(Arrays.equals(restoredPubKey.encoded, keyPair.public.encoded))` and it prints true. – President James K. Polk Nov 29 '21 at 15:04
  • I tried @clausc 's suggestion and couldn't find structural differences; the original and restored keys' encoded representations did actually match, but I'm new to Kotlin and I was comparing them incorrectly. I implemented @ President James K. Polk 's fix and that revealed the issue. Everything is now working properly; thank you both for your help. – jimmyt Nov 29 '21 at 16:39

0 Answers0