[openssl-dev] [openssl.org #3644] Encoding of EC private key is broken in all version

Annie Yousar via RT rt at openssl.org
Wed Jan 7 08:56:15 UTC 2015


Dear all,
all versions of OpenSSL do not encode private EC key correctly.
This shows up every time the private key is at least one byte shorter than
the order. If the private key has full length then the encoding looks
correct.

Consider the following three ECPrivateKey encondings:

-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/oAoGCCqGSM49
AwEHoUQDQgAE9Es5dZoubbcjpvkCSZct/QjpU4Dx/KRw6s0dA+Xt8hS++vzPIjyg
ZfCg207qk/8GohFvyoH3pKlDao2RegLe3g==
-----END EC PRIVATE KEY-----

-----BEGIN EC PRIVATE KEY-----
MGYCAQEEDwAAAAAAAAAAAAAAAAAA/6AKBggqhkjOPQMBB6FEA0IABPRLOXWaLm23
I6b5AkmXLf0I6VOA8fykcOrNHQPl7fIUvvr8zyI8oGXwoNtO6pP/BqIRb8qB96Sp
Q2qNkXoC3t4=
-----END EC PRIVATE KEY-----

-----BEGIN EC PRIVATE KEY-----
MFgCAQEEAf+gCgYIKoZIzj0DAQehRANCAAT0Szl1mi5ttyOm+QJJly39COlTgPH8
pHDqzR0D5e3yFL76/M8iPKBl8KDbTuqT/waiEW/KgfekqUNqjZF6At7e
-----END EC PRIVATE KEY-----

Despite the different base64 encodings all the corresponding private keys
are the same, and therefore any signature made by one can be verified by
any other. You may try it out:
	openssl dgst -sign key1.pem -out ec.sig test.txt
	openssl dgst prverify key2.pem -signature ec.sig test.txt

Try also the OpenSSL "ec" command with the -text option on these keys. You
can see that the private key is always the same (0xFF) and that OpenSSL
recodes the keys. The PEM output is always identical to the last of the
three.

This is not correct. The SEC1 specification OpenSSL is using requires the
full encoding in byte length of the order as e.g. given by the first of
these three encodings.

The patch that works is given as annex. It is applicable to 1.0.1j as well
as to 1.0.2-beta3. There is no strong need to change any other things,
even the documentation ec.pod is already correct (beside the description
of the very strange -modulus option in line 96++.

Attached is also a deeper analysis made by an answer to Douglas E Engert,
who replies to my e-mail on openssl-dev with a different but
non-appropriate subject line (EC key generation is broken in all
versions). Sorry for this misleading subject line, not the key generation
is broken, but the encoding.

Regards,
/Ann.

-------------- next part --------------
A non-text attachment was scrubbed...
Name: 1.0.2-beta3.ec_pk_enc.patch
Type: application/octet-stream
Size: 1227 bytes
Desc: not available
URL: <http://mta.opensslfoundation.net/pipermail/openssl-dev/attachments/20150107/3c134bdd/attachment.obj>
-------------- next part --------------
Hi Douglas,
thank you for pointing me to PCKS#15 encoding of PrivateECKeyAttributes and thanks for spending time on this issue.

Sorry for the long answer, there were many points to be considered.

Keep in mind: The proposed patch does not change any bits on the wire.

Kind regards,
/Ann.


1. PrivateECKeyAttributes vs. ECPrivateKey
OpenSSL generates an EC key with the command
	openssl ecparam -genkey -noout -name prime256v1 
not in the PKCS#15 format but according to a SEC1 structure (cf. crypto/ec/ec_asn1.c line 191), which is defined in http://www.secg.org/sec1-v2.pdf (version 2.0 Annex C.4 p.108):

 ECPrivateKey ::= SEQUENCE {
	version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
	privateKey OCTET STRING,
	parameters [0] ECDomainParameters {{ SECGCurveNames }} OPTIONAL,
	publicKey [1] BIT STRING OPTIONAL
 }

The ASN.1 definition which is used by OpenSSL can be found in ec_asn1.c too (cf. lines 266-271):

 ASN1_SEQUENCE(EC_PRIVATEKEY) = {
	ASN1_SIMPLE(EC_PRIVATEKEY, version, LONG),
	ASN1_SIMPLE(EC_PRIVATEKEY, privateKey, ASN1_OCTET_STRING),
	ASN1_EXP_OPT(EC_PRIVATEKEY, parameters, ECPKPARAMETERS, 0),
	ASN1_EXP_OPT(EC_PRIVATEKEY, publicKey, ASN1_BIT_STRING, 1)
 } ASN1_SEQUENCE_END(EC_PRIVATEKEY)

So OpenSSL uses the structure of SEC1 and refers to SEC1 (cf. also doc/apps/ec.pod lines 31++). 
Why not to follow consequently the requirement of SEC1 (see v2.0 p. 109)?

-----BEGIN LaTeX-----
\item The component \texttt{privateKey} is the private key defined to be the octet string of length $\lceil \log_2 n/8\rceil$ (where $n$ is the order of the curve) obtained from the unsigned integer via the encoding of Section 2.3.7.
-----END LaTeX-----

This is almost the same text as given in RFC 5915. The encoding to be used is given in the Section 2.3.7 of SEC1. The specifcations in PKCS#15 and RFC 3447 are not applicable here.


2. SEC1 vs. RFC 5480
Note the subtle difference in the definitions of SEC1 and RFC 5915. SEC1 uses ECDomainParameters{{ SECGCurveNames }}, restricted to SECGCurveNames, whereas RFC 5480 and RFC 5915 use (unrestricted) ECParameters, defined in RFC 5480. 
Note also that the corrected definition of RFC5915 is

 ECPrivateKey ::= SEQUENCE {
	version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
	privateKey     OCTET STRING,
	parameters [0] ECParameters OPTIONAL,
	publicKey  [1] BIT STRING OPTIONAL
 }

because the type ECParameters defined in RFC 5480 is not parametrized (see http://www.rfc-editor.org/errata_search.php?rfc=5915).
OpenSSL uses the definition of RFC 3279 (ECpkParameters) obsoleted by RFC 5480, which changes only the naming but not the bits on the wire ("ECpkParameters" of RFC 3279 becomes "ECParameters" in RFC 5480, and "ECParameters" of RFC 3279 becomes "SpecifiedECDomain" in RFC 5480 according to SEC1v2).


3. Issue #3465 of RT
http://rt.openssl.org/Ticket/Display.html?id=3465&user=guest&pass=guest
This is *not* releated to the wrong private key encoding. It addresses the fact that the OpenSSL command "ec" fails to parse a generic (without parameters component) ECPrivateKey. But this is not failure at all, because the OpenSSL command ec is certainly restricted to the structures generated and used by OpenSSL itself. OpenSSL does not claim that it parses or accepts an arbitrary ECPrivateKey structure.

The Doctor said "A private key without parameters is unusable anyway". Additionally the RFC 5915 mandates the use of the parameters component (RFC 5915 p. 3):
	Though the ASN.1 indicates that the parameters field is
	OPTIONAL, implementations that conform to this document MUST
	always include the parameters field.
Therefore OpenSSL is fully conformant with the RFC 5915 (in this case 

Truth be told, OpenSSL fails to parse an ECPrivateKey structure even if the parameters field is present but is implicitCurve (aka implicitCA aka NULL). This is in line with RFC 5480 which deprecates the use of NULL parameter in PKIX:
	implicitCurve allows the elliptic curve domain parameters 
	to be inherited.  This choice MUST NOT be used.
But for OpenSSL a private key with NULL parameters is unusable too.

Note that OpenSSL can handle keys with the specifiedCurve parameters and also ECPrivateKey with the optional publicKey field missing.


4. Recoding of private keys
Consider the following "three" ECPrivateKeys:

-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/oAoGCCqGSM49
AwEHoUQDQgAE9Es5dZoubbcjpvkCSZct/QjpU4Dx/KRw6s0dA+Xt8hS++vzPIjyg
ZfCg207qk/8GohFvyoH3pKlDao2RegLe3g==
-----END EC PRIVATE KEY-----
-----BEGIN EC PRIVATE KEY-----
MGYCAQEEDwAAAAAAAAAAAAAAAAAA/6AKBggqhkjOPQMBB6FEA0IABPRLOXWaLm23
I6b5AkmXLf0I6VOA8fykcOrNHQPl7fIUvvr8zyI8oGXwoNtO6pP/BqIRb8qB96Sp
Q2qNkXoC3t4=
-----END EC PRIVATE KEY-----
-----BEGIN EC PRIVATE KEY-----
MFgCAQEEAf+gCgYIKoZIzj0DAQehRANCAAT0Szl1mi5ttyOm+QJJly39COlTgPH8
pHDqzR0D5e3yFL76/M8iPKBl8KDbTuqT/waiEW/KgfekqUNqjZF6At7e
-----END EC PRIVATE KEY-----

Despite the different base64 encodings all the corresponding private keys are the same, so any signature made by one can be verified by any other:
	openssl dgst -sign key1.pem -out ec.sig test.txt
	openssl dgst prverify key2.pem -signature ec.sig test.txt

Try out the OpenSSL "ec" command with the -text option. You can see that OpenSSL recodes the keys and the PEM output is always the last of them.
But this is the wrong one. Only the first encoding conforms to the specification.

This recoding must be corrected to solve the contradiction of description and realization. To conform to its own specification OpenSSL shall recode an ECPrivateKey into the first of the given three variants (fixed length OCTET STRING). The patch that works is given as annex. It is applicable to 1.0.1j as well as to 1.0.2-beta3. There is no strong need to change any other things even the documentation ec.pod is already correct (beside the description of the very strange -modulus option in line 96++ .

Please check the patches carefully.


5. full SEC1/RFC5480 conformance
To acchieve a complete SEC1/RFC5480 conformance some identifiers should be renamed. Apply the following sed commands (in this order) to all files in apps and crypto directory:
s/ec_parameters_st/ec_specifiedDomain_st/g
s/ecpk_parameters_st/ec_parameters_st/g
s/implicitlyCA/implicitCurve/g
s/ECPARAMETERS/SPECIFIEDCURVE/g
s/ECParameters/SpecifiedCurve/g
s/ECPKPARAMETERS/ECPARAMETERS/g
s/ECPKParameters/ECParameters/g
s/ecpkparameters/ecparameters/g
s/parameters2group/specifiedCurve2group/g
s/group2parameters/group2specifiedCurve/g
s/pkparameters2group/ecparameters2group/g
s/group2pkparameters/group2ecparameters/g

There are two header files to be considered (ec.h and pem.h) carefully. Here the old names may be kept as aliases because there are no name collisions, if I'm not wrong. I will provide the full patch on request.

Note also that this renaming does not change any bits on the wire, too.  


More information about the openssl-dev mailing list