Search code examples
javapythoncryptographypython-cryptography

How to load encoded public key using Kotlin PublicKey object in Python using cryptography


I'm currently having problems loading public keys "extracted" from Kotlin in Python. I'm trying to create a functional X25519 EDH between Kotlin and Python, so I need to load public key created by Kotlin code inside Python. The other way around works just fine.

My Kotlin code looks like this

class EllipticDiffieHellman {
    private val keyPairGenerator = KeyPairGenerator.getInstance("XDH")
    private val parameters = NamedParameterSpec("X25519")
    private val keyAgreement = KeyAgreement.getInstance("XDH")
    private val keyFactory = KeyFactory.getInstance("XDH")

    fun generateKeyPair() : KeyPair {
        keyPairGenerator.initialize(parameters)
        return keyPairGenerator.generateKeyPair()
    }

    fun getKeyShare(keyPair: KeyPair) : ByteArray = keyPair.public.encoded

    fun bytesToPubKey(keyShare: ByteArray) : PublicKey = keyFactory.generatePublic(X509EncodedKeySpec(keyShare))

    fun getSharedKey(keyPair: KeyPair, keyShare: PublicKey) : ByteArray {
        keyAgreement.init(keyPair.private)
        keyAgreement.doPhase(keyShare, true)
        return keyAgreement.generateSecret()
    }
}

...

    val EDH = EllipticDiffieHellman()
    val keypair_python = EDH.generateKeyPair()
    File("keyshare_kotlin").writeBytes(EDH.getKeyShare(keypair_python))

When I try loading the same ByteArray using bytesToPubKey, everything works just fine. Now for my Python code (a part of it):

import cryptography.hazmat.primitives.serialization as serialization
with open("keyshare_kotlin", "rb") as f:
    keyshare_kotlin = f.read()
loaded_public_key = serialization.load_der_public_key(keyshare_kotlin)

This doesn't work. I've been trying using other cryptography's loading functions, but none work.

I'm getting ValueError: Could not deserialize key data. The data may be in an incorrect format or it may be encrypted with an unsupported algorithm.

Thanks for any help.


Solution

  • I found a working solution, although as mentioned in the comments - it isn't optimal.

    Bytes generated by Kotlin/Java use different header than bytes generated by Python. While Kotlin's header is 14 bytes long, Python's header is 12 bytes long. This is the only difference between these two "versions" of encoded public keys and while Python is able to load the 12-byte header, it is unable to load Kotlin's 14-byte header.

    Using a header extracted from bytes encoded using cryptography's .public_key().public_bytes(serialization.DER, serialization.SubjectPublicKeyInfo) and replacing the Kotlin's generated header with it it is possible to load the encoded bytes in Python.

    import cryptography.hazmat.primitives.asymmetric.x25519 as x25519
    import cryptography.hazmat.primitives.serialization as serialization
    
    private_key = x25519.X25519PrivateKey.generate()
    public_bytes = private_key.public_bytes(
            encoding=serialization.Encoding.DER,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
    )
    
    with open("keyshare_kotlin", "rb") as f:
            keyshare_kotlin = f.read()
    loaded_keyshare = serialization.load_der_public_key(public_bytes[:12] + keyshare_kotlin[14:])