How do I pass a 44 Bytes x25519 public key created by openssl to CryptoKit which requires a key length of 32 Bytes

2.2k Views Asked by At

Suppose I create a x25519 key pair using openssl, it will output a 64 Bytes private key and the corresponding 44 Bytes Base64 encoded public key which would look like

-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VuBCIEIMBF8S7zUco4bRrMiIuyTcSYU/rAVlNtE8SMYWphUatw
-----END PRIVATE KEY-----


-----BEGIN PUBLIC KEY-----
MCowBQYDK2VuAyEAE0eiiP0PKjy9AVM/0z2ZIZn453WSJNemrQ58HAXDaX0=
-----END PUBLIC KEY-----

Swift CryptoKit is only accepting 32 Bytes for each, private and public key initialisation.

If I understood correctly, the 64 Bytes private key is the seed, where the first 32 Bytes are the actual private key.

Still, using the same principle for the the public key is not working (not very surprising tbh)

The question is now: How do I translate the public key to the 32 Bytes required by Swift CryptoKit?

Here is the non functioning example of using the first 32 Bytes of the base64 decoded public key

let base64PublicKey = Data(base64Encoded: "MCowBQYDK2VuAyEAE0eiiP0PKjy9AVM/0z2ZIZn453WSJNemrQ58HAXDaX0=")!.dropLast(12)

let publicKey = try! Curve25519.KeyAgreement.PublicKey(rawRepresentation: rawPublicKey) 
1

There are 1 best solutions below

0
On BEST ANSWER
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VuBCIEIMBF8S7zUco4bRrMiIuyTcSYU/rAVlNtE8SMYWphUatw
-----END PRIVATE KEY-----

per rfc7468 is a PKCS8-unencrypted PrivateKeyInfo which is encoded in ASN.1 DER and contains data about the algorithm (and in general but not here parameters) as well as the actual key. Running this into openssl asn1parse -i (which automatically de-base64's) gives

    0:d=0  hl=2 l=  46 cons: SEQUENCE
    2:d=1  hl=2 l=   1 prim:  INTEGER           :00
    5:d=1  hl=2 l=   5 cons:  SEQUENCE
    7:d=2  hl=2 l=   3 prim:   OBJECT            :X25519
   12:d=1  hl=2 l=  34 prim:  OCTET STRING      [HEX DUMP]:0420C045F12EF351CA386D1ACC888BB24DC49853FAC056536D13C48C616A6151AB70

The algorithm-specific privatekey is the OCTETSTRING with value at offset 12+2 and length 34, but that actually contains a nested OCTETSTRING encoding whose first two octets are 04=tag and 20=length, so the true privatekey is at offset 16 with length 32 -- or more simply the last 32 bytes.

-----BEGIN PUBLIC KEY-----
MCowBQYDK2VuAyEAE0eiiP0PKjy9AVM/0z2ZIZn453WSJNemrQ58HAXDaX0=
-----END PUBLIC KEY-----

similarly is the SubjectPublicKeyInfo structure defined by X.509 and PKIX which similarly is DER and contains data in addition to the key. Parsing it (with -dump) gives:

    0:d=0  hl=2 l=  42 cons: SEQUENCE
    2:d=1  hl=2 l=   5 cons:  SEQUENCE
    4:d=2  hl=2 l=   3 prim:   OBJECT            :X25519
    9:d=1  hl=2 l=  33 prim:  BIT STRING
      0000 - 00 13 47 a2 88 fd 0f 2a-3c bd 01 53 3f d3 3d 99   ..G....*<..S?.=.
      0010 - 21 99 f8 e7 75 92 24 d7-a6 ad 0e 7c 1c 05 c3 69   !...u.$....|...i
      0020 - 7d

The first octet of a BITSTRING value is used for the number of unused/pad bits, here 00, so the real publickey value is the 33-1=32 octets at offset 9+2+1=12, or again the last 32 bytes.


Ed25519 hashes the privatekey to produce both a 32-byte scalar sometimes called the seed plus a 32-byte value which determines the publickey. This seed can be stored with the privatekey to make signing more efficient, but OpenSSL does not do this for Ed25519, and it doesn't apply at all to X25519.