Closed gshuflin closed 4 months ago
You aren't using either library's support for encoding PKCS#8 private keys, instead you're handrolling your own here and it's broken:
https://github.com/gshuflin/generate-pkcs8-format-key/blob/master/src/main.rs#L50-L60
I already stepped you through the required encoding here:
https://github.com/RustCrypto/formats/issues/1221#issuecomment-1912721338
Note that the private key is encoded as a nested OCTET STRING.
Instead of handrolling the encoding, you can just use ed25519-dalek
's built-in support, which is documented here (which could arguably be better):
https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#pkcs8-key-encoding
SigningKey
impls the EncodePrivateKey
trait: https://docs.rs/ed25519-dalek/latest/ed25519_dalek/struct.SigningKey.html#impl-EncodePrivateKey-for-SigningKey
You can just call SigningKey::to_pkcs8_der
here to encode the key as DER: https://github.com/gshuflin/generate-pkcs8-format-key/blob/master/src/main.rs#L32
I've updated my repo with a new method that calls write_pkcs8_[der|pem]_file
from the ed25519_dalek::pkcs8::EncodePrivateKey
trait (https://github.com/gshuflin/generate-pkcs8-format-key/commit/7846087f4a09558070eaf51f9abae349bc52c27f) I'm still seeing the following error:
$ openssl pkcs8 -topk8 -in new-dalek.der
Could not read key from new-dalek.der
80C5730201000000:error:1608010C:STORE routines:ossl_store_handle_load_result:unsupported:crypto/store/store_result.c:151:
I also note that these methods do not offer an argument to input an encryption password, so I'm not sure how I would generate an encrypted version of these generated ED25519 keys.
It's possible that OpenSSL lacks support for PKCS#8 v2 keys which are specified in RFC8410 Section 7, which is what ed25519-dalek implements.
Here is what your example generates:
$ openssl asn1parse -in new-dalek.pem
0:d=0 hl=2 l= 81 cons: SEQUENCE
2:d=1 hl=2 l= 1 prim: INTEGER :01
5:d=1 hl=2 l= 5 cons: SEQUENCE
7:d=2 hl=2 l= 3 prim: OBJECT :ED25519
12:d=1 hl=2 l= 34 prim: OCTET STRING [HEX DUMP]:042015C1EA86B9108DB710ED44ED9A32469742E62BA5DCF308775DAE955FF60E1C70
48:d=1 hl=2 l= 33 prim: cont [ 1 ]
Here is a key generated by OpenSSL:
$ openssl genpkey -algorithm ed25519 -out privkey.pem
$ openssl asn1parse -in privkey.pem
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 :ED25519
12:d=1 hl=2 l= 34 prim: OCTET STRING [HEX DUMP]:0420A47446132E82EADC1CCEABE2E015A41B1135980146D246352A3B3EE8C13157A4
The main differences are the lower version and the absence of a public key.
If you remove the public key from the serialization, OpenSSL may be able to parse it. Note that this does not conform to RFC8410.
The ed25519::pkcs8::KeypairBytes
type makes the public_key
field an Option
, so you can just set it to None
.
I wasn't able to get OpenSSL to parse a PKCS#8 v2 example from RFC8410, but it is able to parse the PKCS#8 v1 example.
The RFC includes the following note:
NOTE: There exist some private key import functions that have not picked up the new ASN.1 structure OneAsymmetricKey that is defined in RFC7748]. This means that they will not accept a private key structure that contains the public key field. This means a balancing act needs to be done between being able to do a consistency check on the key pair and widest ability to import the key.
So arguably ed25519-dalek should produce PKCS#8 v1 by default (a.k.a. OpenSSL is why we can't have nice things)
I've updated my example repo to manually convert the ed25519_dalek::SigningKey
bytes into a KeypairBytes
, and invoke the file-writing methods on that: https://github.com/gshuflin/generate-pkcs8-format-key/commit/02701d608f61a25f711e2ddb36e005423579018f
With these new keys, I can run:
$ openssl pkey -in new-dalek.der
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEII8hYgQsBf887pTJqi1E641YxhuSDvHwTezaH0iklGMr
-----END PRIVATE KEY-----
$ openssl pkey -in new-dalek.der -pubout
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA8d+4e60CoP/Lj5EaSC2uEwdeu89H97FfbZksMX4mMUw=
-----END PUBLIC KEY-----
Interestingly, if I run:
$ openssl pkcs8 -in new-dalek.der -topk8
Enter Encryption Password:
Verifying - Enter Encryption Password:
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAi5N5rFy/E3xgICCAAw
DAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEHvicoJK+HA2OHJT9XwKLg4EQH7T
SqMxqHhbUUvGw3l6fCmzEaOf6Y2fHA5pMmrbYzzLOL8OS5ITmqJ27y3AW4XIOr9k
2q0BcGFY/dsIoybe/G4=
-----END ENCRYPTED PRIVATE KEY-----
It prompts me for an encryption password, and then accepts any password at all, before printing out the following output. I'm still not sure how I would specify a PKCS5 password in code when I generate the key. I'm also not sure I understand the difference between PKCS8 v1 and v2, or the difference between running openssl pkey
and openssl pkcs8 -topk8
$ openssl pkcs8 -in new-dalek.der -topk8
You're giving it an unencrypted key, it's prompting for a password, and then producing an encrypted PKCS#8 document.
Adding -nocrypt
will make it operate on unencrypted PKCS#8 keys.
Otherwise it looks like it's working.
I'm also not sure I understand the difference between PKCS8 v1 and v2, or the difference between running openssl pkey and openssl pkcs8 -topk8
PKCS#8 v2 includes a public key in addition to a private key (i.e. a keypair).
openssl pkey
produces the public key, whereas openssl pkcs8 -topk8
produces a private key, and unless -nocrypt
is specified, an encrypted one.
Aside from making a tracking issue on dalek-cryptography to suggest using PKCS#8 v1 for OpenSSL interop, I think this issue can be closed.
This is a continuation of some of the issues previously discussed in this thread: https://github.com/RustCrypto/formats/issues/1221
I have an example repo here: https://github.com/gshuflin/generate-pkcs8-format-key that uses the
ed25519
andpkcs8
crates to generate ED25519 keypairs, and write them to files. Regardless of whether I output PEM or DER format files, or whether I useed25519_zebra
ored25519_dalek
as the underlying library, the files generated are invalid as far asopenssl
is concerned, yielding output such as:How do I correctly use these rust crates to generate valid PEM or DER keyfiles?