Search code examples
opensslderx25519

OpenSSL: try to decode DER data


I have the following python code that creates a piece of DER data "x25519_pubic_der".

#!/usr/bin/python3

from cryptography.hazmat.primitives.asymmetric import x25519
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat

x25519_key = x25519.X25519PrivateKey.generate()
x25519_public_der = x25519_key.public_key().public_bytes(Encoding.DER,
                                                         PublicFormat.SubjectPublicKeyInfo)

I try to decode this piece of data back to its original binary bits using C code, but not working. Below is one test program (assuming der data is transferred correctly, also the length).

const unsigned char* ptr = x25519_public_der;
ASN1_OCTET_STRING* octet_string = d2i_ASN1_OCTET_STRING(NULL, &ptr, x25519_public_der_len);
if (!octet_string) {
   fprintf(stderr, "Error decoding DER data to binary bytes\n");
   return 1;
}

I suspect PublicFormat.SubjectPublicKeyInfo may add some extra encoding, or I am not using the right d2i_ function (?), only a guess...

Basically x25519_public_der contains 44 bytes and I want to restore it to 32 bytes using C programming. I think this question may be relevant, How do I pass a 44 Bytes x25519 public key created by openssl to CryptoKit which requires a key length of 32 Bytes, but I do not have enough background to implement it in C.


Solution

  • Since the raw public key is located at the end of the SPKI/DER X25519 key, the last 32 bytes can simply be used.


    A more general approach is to import the SPKI/DER key as EVP_PKEY and extract the raw key with EVP_PKEY_get_raw_public_key, e.g. as of OpenSSL v3.0 (for the sake of simplicity, without exception handling):

    const char spki_der[] = { 0x30, 0x2A, 0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, 0x6E, 0x03, 0x21, 0x00, 0xBB, 0x13, 0xF6, 0x6B, 0x4E, 0xC0, 0x9E, 0xE2, 0xD5, 0xC6, 0xE5, 0x49, 0xDF, 0xE9, 0x06, 0x41, 0x4A, 0x79, 0x99, 0x98, 0xE1, 0xE3, 0x93, 0x46, 0x13, 0xD6, 0xBE, 0xD3, 0xC2, 0xEE, 0x9E, 0x66 };
    const unsigned char* data = (const unsigned char*)spki_der;
    size_t datalen = sizeof(spki_der);
    
    // Import SPKI/DER X25519 key
    OSSL_DECODER_CTX* dctx;
    EVP_PKEY* pkeyPub = NULL;
    dctx = OSSL_DECODER_CTX_new_for_pkey(&pkeyPub, "DER", NULL, "X25519", OSSL_KEYMGMT_SELECT_PUBLIC_KEY, NULL, NULL);
    OSSL_DECODER_from_data(dctx, &data, &datalen);
    
    // Export raw X25519 key (32 bytes)
    unsigned char rawPub[32];
    size_t rawPubLen = 32;
    EVP_PKEY_get_raw_public_key(pkeyPub, rawPub, &rawPubLen); // rawPub: 0xbb13f66b4ec09ee2d5c6e549dfe906414a799998e1e3934613d6bed3c2ee9e66
    

    For the sake of completeness:
    OSSL_DECODER_CTX_new_for_pkey() can be used to import public (5th parameter: OSSL_KEYMGMT_SELECT_PUBLIC_KEY) and private (5th parameter: OSSL_KEYMGMT_SELECT_KEYPAIR) keys of different types (4th parameter, e.g. "X25519") and encodings (2nd parameter, e.g. "PEM" or "DER").
    For private keys, EVP_PKEY_get_raw_private_key() must be used to export the raw key.
    Depending on OSSL_DECODER_from_..., different data sources can be chosen.