# Ed25519 PKCS8 private key example from IETF draft seems malformed

Malformed PKCS8 Key

An example of the same Ed25519 private key encoded with an attribute
and the public key:

-----BEGIN PRIVATE KEY-----
MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB
Z9w7lshQhqowtrbLDFw4rXAxZuE=
-----END PRIVATE KEY-----

The same item dumped as ASN.1 yields:

0 114: SEQUENCE {
2   1:   INTEGER 1
5   5:   SEQUENCE {
7   3:     OBJECT IDENTIFIER '1 3 101 112'
:     }
12  34:   OCTET STRING, encapsulates {
14  32:     OCTET STRING D4 EE 72 DB F9 13 58 4A D5 B6 D8 F1 F7
69 F8 AD 3A FE 7C 28 CB F1 D4 FB E0 97 A8 8F 44
75 58 42
:     }
48  31:   [0] {
50  29:     SEQUENCE {
52  10:       OBJECT IDENTIFIER '1 2 840 113549 1 9 9 20'
64  15:       SET {
66  13:         UTF8String 'Curdle Chairs'
:         }
:       }
:     }
81  33:   [1] 00 19 BF 44 09 69 84 CD FE 85 41 BA C1 67 DC 3B
96 C8 50 86 AA 30 B6 B6 CB 0C 5C 38 AD 70 31 66
E1
:   }


The last bit doesn't seem to be valid be a valid PKCS8 private key. Here's the ASN.1 definition for private keys from PKCS8:

PrivateKeyInfo ::= SEQUENCE {
version                   Version,
privateKeyAlgorithm       PrivateKeyAlgorithmIdentifier,
privateKey                PrivateKey,
attributes           [0]  IMPLICIT Attributes OPTIONAL }


The ASN.1 definition has one context-specific tag with a tag number of 0 whereas the private key my first snippet has has two context-specific tags - one with a tag number of 0 and one with a tag number of 1.

Do the ASN.1 standards permit the appending of arbitrary strings to an ASN.1 encoded object? Because it seems that that's what the "private key encoded with an attribute and the public key" is doing...

Alternate Approaches

It seems to me that if the authors wanted to make the private key include the public key they could add the public key live in the PrivateKeyAlgorithmIdentifier. Quoting the definition for that:

PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier

AlgorithmIdentifier  ::=  SEQUENCE  {
algorithm               OBJECT IDENTIFIER,
parameters              ANY DEFINED BY algorithm OPTIONAL  }


(AlgorithmIdentifier is defined in RFC5280)

DSA private keys do this. The DSA Parameters $p$, $q$ and $g$ are defined in the parameters field of the AlgorithmIdentifier. So it's not like use of that field isn't without precedent (see RFC5912, for example).

Another possibility: just hijack ECPrivateKey. Just make it so Ed25519 can only be utilized with a NamedCurve and parse publicKey and privateKey differently (as specified in RFC8032) when Ed25519 is being used...

• – jww Dec 18 '18 at 18:33

That example private key is not encoded with PKCS#8, but with the format described in RFC 5958, which is supposed to replace PKCS#8 (at least, in IETF parlance, RFC 5958 "obsoletes" RFC 5208, which is a copy of PKCS#8 v1.2). The ASN.1 type if called OneAsymmetricKey:

 OneAsymmetricKey ::= SEQUENCE {
version                   Version,
privateKeyAlgorithm       PrivateKeyAlgorithmIdentifier,
privateKey                PrivateKey,
attributes            [0] Attributes OPTIONAL,
...,
[[2: publicKey        [1] PublicKey OPTIONAL ]],
...
}


The structure is similar to that of PrivateKeyInfo in PKCS#8, but with the extra publicKey field (optional) at the end. Also note that the version field, which is an INTEGER, has value 0 for PKCS#8, but value 1 for OneAsymmetricKey; this is meant to allow some level of backward compatibility, in the sense that a decoder for OneSymmetricKey can accept an encoded PrivateKeyInfo, and will use the version field to apply the proper semantics (i.e. to decode the contents of the private key blob).

I must say that I have, so far, never encountered a OneSymmetricKey structure in the wild. The openssl command-line tool encodes keys in PKCS#8, and applications tend to expect that. RFC 5958 has status "proposed standard", but sometimes you cannot enforce acceptance.

• Scratch that last comment. The freaking RFC is writing the byte arrays to an ASN.1 octet string in little-endian format instead of network byte order. Why do they do that crap??? For an organization that is supposed to promote interop they do some really counter-intuitive things that just adds to confusion. – jww Dec 18 '18 at 21:41