How to generate PEM from Curve25519 public key

I want to use ECC (curve25519) with Diffie-Hellman Key exchange. Backend is using ECC with X9.62 and PKCS#8 encoding. I want to achieve same on iOS so that I can fetch data from backed, decrypt it and show it to user.

I tried this code but didn't work

func getPEM() -> String {
    let keyPair = Curve25519.Signing.PrivateKey()
    let pubKey = keyPair.publicKey
    let pem = "-----BEGIN PUBLIC KEY-----\(pubKey.rawRepresentation.base64EncodedString())-----END PUBLIC KEY-----"
    
    return pem
}

After searching on Google I found that In order to get PEM we need DER and ASN1 from my public key but they are not supported by CryptoKit.

Android is able to get right PEM using bouncycastle. Just for reference I am posting snippet from android code base.

import org.bouncycastle.asn1.x9.X9ECParameters
import org.bouncycastle.crypto.ec.CustomNamedCurves
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.*
import java.security.spec.ECParameterSpec
import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec

object CryptoKeyGenerator {

    init {
        Security.removeProvider(BOUNCY_CASTLE_IDENTIFIER)
        Security.addProvider(BouncyCastleProvider())
    }

    fun getClientKeyMaterial(): String {
        val keyPair = generateEphemeralKeyPair()
        val pemEncodedPublicKey = getPEMEncodedStream(keyPair.public, false)
        return pemEncodedPublicKey
    }

    private fun getPEMEncodedStream(key: Key, privateKey: Boolean): String {
        val pkcS8EncodedKeySpec = PKCS8EncodedKeySpec(key.encoded)
        val stringBuilder = StringBuilder()
        val keyType = if(privateKey) PRIVATE_KEY else PUBLIC_KEY
        stringBuilder.append(KEY_HEADER_START + keyType + KEY_HEADER_END)
        stringBuilder.append(CryptoUtils.getBase64Encoded(pkcS8EncodedKeySpec.encoded))
        stringBuilder.append(KEY_FOOTER_START + keyType + KEY_HEADER_END)
        return stringBuilder.toString()
    }

    /**
    * This method generates an ECC KeyPair with Curve25519 specs
    */
    private fun generateEphemeralKeyPair(): KeyPair {
        val keyPairGenerator = KeyPairGenerator.getInstance(EC_ALGO_IDENTIFIER, BOUNCY_CASTLE_IDENTIFIER)
        val eccParameters: X9ECParameters = CustomNamedCurves.getByName(ECC_CURVE_SPEC)
        val eccSpec: ECParameterSpec = EC5Util.convertToSpec(eccParameters)
        keyPairGenerator.initialize(eccSpec)
        return keyPairGenerator.generateKeyPair()
    }
}

Android generated PEM of public key look like this

-----BEGIN PUBLIC KEY-----MIIBMTCB6gYHKoZIzj0CATCB3gIBATArBgcqhkjOPQEBAiB/////////////////////////////////////////7TBEBCAqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqYSRShRAQge0Je0Je0Je0Je0Je0Je0Je0Je0Je0Je0JgtenHcQyGQEQQQqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq0kWiCuGaG4oIa04B7dLHdI0UySPU1+bXxhsinpxaJ+ztPZAiAQAAAAAAAAAAAAAAAAAAAAFN753qL3nNZYEmMaXPXT7QIBCANCAAQwsdIRTVn2+6rlgqAhVvx7ERj/Oku0wHmZZU1OST617h95ygSP5zJOa9lNiKqZMArjtJh7yQ4rg7kUq08Nv8+Q-----END PUBLIC KEY-----

Other references: This repo has code for C, Java and nodejs https://github.com/Sahamati/rahasya

Replies

More detail about android key

EC Public Key [af:96:ed:d9:35:69:af:58:42:5d:97:6e:67:52:ac:43:24:81:de:de]
            X: 2f1404faba773de88bcb0307746c69c49349af8f8588620791c80346bcf4c668
            Y: 287736f1385dc588b098375694b6311b95c78a4a0c83aa5ebe5e6daac50f188e

Hex String:

308201313081ea06072a8648ce3d02013081de020101302b06072a8648ce3d010102207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed304404202aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa984914a14404207b425ed097b425ed097b425ed097b425ed097b425ed097b4260b5e9c7710c8640441042aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad245a20ae19a1b8a086b4e01edd2c7748d14c923d4d7e6d7c61b229e9c5a27eced3d902201000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed020108034200042f1404faba773de88bcb0307746c69c49349af8f8588620791c80346bcf4c668287736f1385dc588b098375694b6311b95c78a4a0c83aa5ebe5e6daac50f188e

PEM Encoded String:

-----BEGIN PUBLIC KEY-----MIIBMTCB6gYHKoZIzj0CATCB3gIBATArBgcqhkjOPQEBAiB/////////////////////////////////////////7TBEBCAqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqYSRShRAQge0Je0Je0Je0Je0Je0Je0Je0Je0Je0Je0JgtenHcQyGQEQQQqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq0kWiCuGaG4oIa04B7dLHdI0UySPU1+bXxhsinpxaJ+ztPZAiAQAAAAAAAAAAAAAAAAAAAAFN753qL3nNZYEmMaXPXT7QIBCANCAAQvFAT6unc96IvLAwd0bGnEk0mvj4WIYgeRyANGvPTGaCh3NvE4XcWIsJg3VpS2MRuVx4pKDIOqXr5ebarFDxiO-----END PUBLIC KEY----

Android generated PEM of public key look like this

That is not a Curve25519 public key. Consider this:

% base64 -D > tmp.asn1
MIIB…v8+Q
% dumpasn1 -p -a tmp.asn1
SEQUENCE {
  SEQUENCE {
    OBJECT IDENTIFIER ecPublicKey (1 2 840 10045 2 1)
    SEQUENCE {
      INTEGER 1
      SEQUENCE {
        OBJECT IDENTIFIER prime-field (1 2 840 10045 1 1)
        INTEGER
          7F FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
          FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ED
        }
      SEQUENCE {
        OCTET STRING
          2A AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA
          AA AA AA AA AA AA AA AA AA AA AA 98 49 14 A1 44
        OCTET STRING
          7B 42 5E D0 97 B4 25 ED 09 7B 42 5E D0 97 B4 25
          ED 09 7B 42 5E D0 97 B4 26 0B 5E 9C 77 10 C8 64
        }
      OCTET STRING
        04 2A AA AA AA AA AA AA AA AA AA AA AA AA AA AA
        AA AA AA AA AA AA AA AA AA AA AA AA AA AA AD 24
        5A 20 AE 19 A1 B8 A0 86 B4 E0 1E DD 2C 77 48 D1
        4C 92 3D 4D 7E 6D 7C 61 B2 29 E9 C5 A2 7E CE D3
        D9
      INTEGER
        10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
        14 DE F9 DE A2 F7 9C D6 58 12 63 1A 5C F5 D3 ED
      INTEGER 8
      }
    }
  BIT STRING
    04 30 B1 D2 11 4D 59 F6 FB AA E5 82 A0 21 56 FC
    7B 11 18 FF 3A 4B B4 C0 79 99 65 4D 4E 49 3E B5
    EE 1F 79 CA 04 8F E7 32 4E 6B D9 4D 88 AA 99 30
    0A E3 B4 98 7B C9 0E 2B 83 B9 14 AB 4F 0D BF CF
    90
  }

Note the final BIT STRING, which is an 04 followed by X and Y values. This makes it a NIST P-256 key, albeit in some packaging that I don’t quite recognise.

Compare the above with the ECPrivateKey example in On Cryptographic Key Formats.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"