openssled25519

How can I extract the 32 byte ED25519 public key from a PEM file? And how can I create a public PEM file from the 32 bytes key?


I generated an ED25519 key pair using OpenSSL as follows:

openssl genpkey -algorithm ed25519 -out private.pem
openssl pkey -in private.pem -pubout -out public.pem

Based on this answer, I managed to extract the 32 bytes ED25519 key from the private PEM file with:

openssl asn1parse -in private.pem -offset 14

Output:

0:d=0  hl=2 l=  32 prim: OCTET STRING      [HEX DUMP]:1E9D38B5274152A78DFF1A86FA464CEADC1F4238CA2C17060C3C507349424A34

So the 32 byte key (encoded as 64 hex characters) is:

1E9D38B5274152A78DFF1A86FA464CEADC1F4238CA2C17060C3C507349424A34

How can I do the same for the PEM public file public.pem?

And how could I create a valid public PEM file for that 32-byte key?

Note: My ultimate goal is to create a public ED25519 PEM file from a known 32 byte public ED25519 key. (without knowing the private key)


Solution

  • How can I do the same for the PEM public file public.pem?

    Method 1: General method

    OpenSSL provides a built-in method to print out the 32-byte hex keys for both private and public keys, whether they are in the DER or PEM format:

    openssl pkey [-pubin] [-inform pem/der] -in [KEYFILE] [-noout] -text
    

    The main options are:

    See the OpenSSL pkey documentation for more details.

    Example usage:

    ##### Print out 32-byte keys in hex for private/public DER/PEM keys:
    echo "=== Private DER key ==="
    openssl pkey -inform der -in private.der -noout -text
    echo "=== Private PEM key ==="
    openssl pkey -inform pem -in private.pem -noout -text
    echo "=== Public DER key ==="
    openssl pkey -pubin -inform der -in public.der -noout -text
    echo "=== Public PEM key ==="
    openssl pkey -pubin -inform pem -in public.pem -noout -text
    

    Corresponding output:

    === Private DER key ===
    ED25519 Private-Key:
    priv:
        b7:4e:b3:2c:d8:05:ed:82:a3:58:0e:fa:59:21:cd:
        b2:0a:ad:7f:4b:f6:41:09:05:7e:84:61:11:55:5e:
        a3:e9
    pub:
        d1:64:89:3b:1c:e1:17:17:ab:48:22:4f:fe:26:10:
        2e:9c:9c:13:61:36:90:ee:ed:38:29:ab:a6:7d:6f:
        b7:87
    === Private PEM key ===
    ED25519 Private-Key:
    priv:
        b7:4e:b3:2c:d8:05:ed:82:a3:58:0e:fa:59:21:cd:
        b2:0a:ad:7f:4b:f6:41:09:05:7e:84:61:11:55:5e:
        a3:e9
    pub:
        d1:64:89:3b:1c:e1:17:17:ab:48:22:4f:fe:26:10:
        2e:9c:9c:13:61:36:90:ee:ed:38:29:ab:a6:7d:6f:
        b7:87
    === Public DER key ===
    ED25519 Public-Key:
    pub:
        d1:64:89:3b:1c:e1:17:17:ab:48:22:4f:fe:26:10:
        2e:9c:9c:13:61:36:90:ee:ed:38:29:ab:a6:7d:6f:
        b7:87
    === Public PEM key ===
    ED25519 Public-Key:
    pub:
        d1:64:89:3b:1c:e1:17:17:ab:48:22:4f:fe:26:10:
        2e:9c:9c:13:61:36:90:ee:ed:38:29:ab:a6:7d:6f:
        b7:87
    

    If you leave out the -noout option, you simply also get the PEM (ASCII version) of the key printed out as well.

    Example:

    $ openssl pkey -pubin -inform pem -in public.pem  -text
    -----BEGIN PUBLIC KEY-----
    MCowBQYDK2VwAyEA0WSJOxzhFxerSCJP/iYQLpycE2E2kO7tOCmrpn1vt4c=
    -----END PUBLIC KEY-----
    ED25519 Public-Key:
    pub:
        d1:64:89:3b:1c:e1:17:17:ab:48:22:4f:fe:26:10:
        2e:9c:9c:13:61:36:90:ee:ed:38:29:ab:a6:7d:6f:
        b7:87
    $ cat public.pem
    -----BEGIN PUBLIC KEY-----
    MCowBQYDK2VwAyEA0WSJOxzhFxerSCJP/iYQLpycE2E2kO7tOCmrpn1vt4c=
    -----END PUBLIC KEY-----
    

    Method 2: Extract 32-byte keys using xxd

    If you need to store the 32-byte keys in a variable, it can be more convenient to convert the key files to the binary DER format and then use xxd to extract them. The 32-byte keys are simply the last 32 bytes of the DER files, whether it is the private or public key.

    Create DER files from PEM files if necessary

    Here we use the openssl pkey utility again:

    # For private keys
    openssl pkey -inform pem -in private.pem -outform der -out private.der
    # For public keys
    openssl pkey -pubin -inform pem -in public.pem -pubout -outform der -out public.der
    

    New relevant options:

    Getting the last 32-bytes using xxd

    Here is what the contents of the private and public DER files look like in hexadecimal:

    $ xxd private.der
    00000000: 302e 0201 0030 0506 032b 6570 0422 0420  0....0...+ep.".
    00000010: b74e b32c d805 ed82 a358 0efa 5921 cdb2  .N.,.....X..Y!..
    00000020: 0aad 7f4b f641 0905 7e84 6111 555e a3e9  ...K.A..~.a.U^..
    $ xxd public.der
    00000000: 302a 3005 0603 2b65 7003 2100 d164 893b  0*0...+ep.!..d.;
    00000010: 1ce1 1717 ab48 224f fe26 102e 9c9c 1361  .....H"O.&.....a
    00000020: 3690 eeed 3829 aba6 7d6f b787            6...8)..}o..
    

    The last 32-bytes in each case are the keys, i.e. in this case:

    To extract them and save them to a variable, you can use this command:

    xxd  -plain -cols 32 -s -32 DERFILE
    

    This will print out the last 32 bytes (-s -32: seek 32 bytes from the end) on one line (-cols 32: 32 byte columns per line), without the extra offset and ASCII information that xxd prints ou by default (-plain option).

    Example usage:

    $ xxd  -plain -cols 32 -s -32 private.der
    b74eb32cd805ed82a3580efa5921cdb20aad7f4bf64109057e846111555ea3e9
    $ xxd  -plain -cols 32 -s -32 public.der
    d164893b1ce11717ab48224ffe26102e9c9c13613690eeed3829aba67d6fb787
    

    You can check that these correspond to the openssl pkey output we got earlier with the -text option.

    Store output in a variable:

    $ hex=$(xxd  -plain -cols 32 -s -32 public.der)
    $ echo "32-byte hex public key: ${hex}"
    32-byte hex public key: d164893b1ce11717ab48224ffe26102e9c9c13613690eeed3829aba67d6fb787
    

    And how could I create a valid public PEM file for that 32-byte key?

    Based on the xxd output of the DER files seen before, we know that DER files are simply a binary header followed by the 32-byte key. The headers are different for private and public keys, but independent of the 32-byte keys, i.e. any such header followed by 32 bytes is a valid DER file.

    The headers are:

    This makes creating the DER files easy and they can then be converted to PEM files using openssl pkey.

    Note: I do not know why the headers are this way, but it seems to have to do with the RFC 8410 specification. See also this stackoverflow answer.

    Creating a 32-bytes public PEM key

    Let's assume our 32-byte public key is d164893b1ce11717ab48224ffe26102e9c9c13613690eeed3829aba67d6fb787 in hex format.

    First, we create a valid DER file by simply appending those bytes to the standard header of a DER file:

    header="30:2A:30:05:06:03:2B:65:70:03:21:00" # Necessary header for the OpenSSL DER format
    hex="d164893b1ce11717ab48224ffe26102e9c9c13613690eeed3829aba67d6fb787"
    echo "${header}${hex}" | xxd -revert -plain > public.manual.der
    

    Next, we convert it to the PEM format with:

    openssl pkey -pubin -inform der -in public.manual.der -pubout -out public.manual.pem
    

    Creating a 32-bytes private PEM key

    Let's assume our 32-byte private key is b74eb32cd805ed82a3580efa5921cdb20aad7f4bf64109057e846111555ea3e9 in hex format.

    First, we create a valid DER file by simply appending those bytes to the standard header of a DER file:

    header="30:2e:02:01:00:30:05:06:03:2b:65:70:04:22:04:20" # Necessary header for the OpenSSL DER format
    hex="b74eb32cd805ed82a3580efa5921cdb20aad7f4bf64109057e846111555ea3e9"
    echo "${header}${hex}" | xxd -revert -plain > private.manual.der
    

    Next, we convert it to the PEM format with:

    openssl pkey -inform der -in private.manual.der -out private.manual.pem
    

    Test the manually created PEM files

    $ echo "Hello world!" > data.txt
    $ openssl pkeyutl -sign -inkey private.manual.pem -rawin -in data.txt -out data.txt.sig
    $ openssl pkeyutl -verify -pubin -inkey public.manual.pem -sigfile data.txt.sig -rawin -in data.txt
    Signature Verified Successfully