Python import of ECC private key in PEM encoding fails

Question:

I’m running Python version 3.8.2 and using pycryptodome version 3.9.9 to import an ECC private key in PEM encoding for later signing some data.

The following EC private key is a sample key, and I’m using it for several cross-platform projects [e.g. Java, PHP, NodeJs] and it works without any problem (it’s a NIST P-256 / secp256r1-key) key:

ecprivatekey.pem:

-----BEGIN EC PRIVATE KEY-----
MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCAU2f8tzo99Z1HoxJlY
96yXUhFY5vppVjw1iPKRfk1wHA==
-----END EC PRIVATE KEY-----

Using this key in Python is failing:

Invalid DER encoding inside the PEM file

Using a ASN1-dumper I see:

  0  65: SEQUENCE {
  2   1:   INTEGER 0
  5  19:   SEQUENCE {
  7   7:     OBJECT IDENTIFIER ecPublicKey (1 2 840 10045 2 1)
 16   8:     OBJECT IDENTIFIER prime256v1 (1 2 840 10045 3 1 7)
       :     }
 26  39:   OCTET STRING, encapsulates {
 28  37:     SEQUENCE {
 30   1:       INTEGER 1
 33  32:       OCTET STRING
       :         14 D9 FF 2D CE 8F 7D 67 51 E8 C4 99 58 F7 AC 97
       :         52 11 58 E6 FA 69 56 3C 35 88 F2 91 7E 4D 70 1C
       :       }
       :     }
       :   }

Now I’m converting this PEM-file to a DER-file using OpenSSL and encode the result in Base64 for using in Python:

openssl ec -in ecprivatekey.pem -outform DER -out ecprivatekey.der
openssl enc -base64 -in ecprivatekey.der -out ecprivatekey.der.base64

This is the result:

MDECAQEEIBTZ/y3Oj31nUejEmVj3rJdSEVjm+mlWPDWI8pF+TXAcoAoGCCqGSM49AwEH

Running my import it imports the key successfully:

EccKey(curve='NIST P-256', point_x=93061505133516819612094413624227760091937004899246228970231210633982641184160, point_y=83370390147869481338300161558578623699120044123289243047585106101294907287413, d=9431423964991629169983079041344798030398447908105071875075159616703093895196)

The last step is to "reconvert" the DER encoded file to a PEM-encoded one:

openssl ec -inform DER -in ecprivatekey.der -outform PEM -out ecprivatekey2.pem

These are the results of the conversion and the ASN1-dump:

-----BEGIN EC PRIVATE KEY-----
MDECAQEEIBTZ/y3Oj31nUejEmVj3rJdSEVjm+mlWPDWI8pF+TXAcoAoGCCqGSM49
AwEH
-----END EC PRIVATE KEY-----

  0  49: SEQUENCE {
  2   1:   INTEGER 1
  5  32:   OCTET STRING
       :     14 D9 FF 2D CE 8F 7D 67 51 E8 C4 99 58 F7 AC 97
       :     52 11 58 E6 FA 69 56 3C 35 88 F2 91 7E 4D 70 1C
 39  10:   [0] {
 41   8:     OBJECT IDENTIFIER prime256v1 (1 2 840 10045 3 1 7)
       :     }
       :   }

The reconverted key can get imported:

EccKey(curve='NIST P-256', point_x=93061505133516819612094413624227760091937004899246228970231210633982641184160, point_y=83370390147869481338300161558578623699120044123289243047585106101294907287413, d=9431423964991629169983079041344798030398447908105071875075159616703093895196)

So my question: what is "wrong" with my EC private key so that it runs within Java / PHP / NodeJs-Crypto / WebCrypto but not in Python ? Or much better: how can I import my existing EC private key in Python without any further (external) conversion ?

This is the full source of my import test program:

from Crypto.PublicKey import ECC
import base64

print("Python import EC private keyn")

# trying to import the original EC private key
ecPrivateKeyPem = """-----BEGIN EC PRIVATE KEY-----
MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCAU2f8tzo99Z1HoxJlY
96yXUhFY5vppVjw1iPKRfk1wHA==
-----END EC PRIVATE KEY-----
"""
try:
  ecPrivateKey = ECC.import_key(ecPrivateKeyPem)  
  print(ecPrivateKey)
except ValueError as e:
  print(e)
#error: Invalid DER encoding inside the PEM file

# import of the DER encoded EC private key runs:
ecPrivateKeyDerBase64 = """MDECAQEEIBTZ/y3Oj31nUejEmVj3rJdSEVjm+mlWPDWI8pF+TXAcoAoGCCqGSM49AwEH"""
ecPrivateKeyDer = base64.decodebytes(ecPrivateKeyDerBase64.encode("ascii"))
try:
  ecPrivateKey = ECC.import_key(ecPrivateKeyDer)
  print("necPrivateKeyDer")  
  print(ecPrivateKey)
except ValueError as e:
  print(e)

ecPrivateKeyPem2 = """-----BEGIN EC PRIVATE KEY-----
MDECAQEEIBTZ/y3Oj31nUejEmVj3rJdSEVjm+mlWPDWI8pF+TXAcoAoGCCqGSM49
AwEH
-----END EC PRIVATE KEY-----
"""
try:
  ecPrivateKey2 = ECC.import_key(ecPrivateKeyPem2) 
  print("necPrivateKeyPem2")  
  print(ecPrivateKey2)
except ValueError as e:
  print(e)
Asked By: Michael Fehr

||

Answers:

The reason for the failure to import my EC private key is very simple – my EC key contained (only) the private key but not the public key. This seems to be okay for Java / PHP / NodeJs-Crypto / WebCrypto (they "derive" the public key under the hood) but not in Python.

I ran into the same problem when I tried to import my EC private key in Dart (using PointyCastle & Basics_Utils), after that I generated a complete new key pair with OpenSSL, the PKCS#8 encoded key has this structure (the additional BIT STRING at the end is the public key):

  0 135: SEQUENCE {
  3   1:   INTEGER 0
  6  19:   SEQUENCE {
  8   7:     OBJECT IDENTIFIER ecPublicKey (1 2 840 10045 2 1)
 17   8:     OBJECT IDENTIFIER prime256v1 (1 2 840 10045 3 1 7)
       :     }
 27 109:   OCTET STRING, encapsulates {
 29 107:     SEQUENCE {
 31   1:       INTEGER 1
 34  32:       OCTET STRING
       :         72 23 ED FE 0B A5 CF 0E FF 5D ED 76 60 EB BF BC
       :         B5 20 21 46 7E EE 01 A8 E5 59 26 53 40 7E 81 45
 68  68:       [1] {
 70  66:         BIT STRING
       :           04 31 91 E7 B7 50 F5 B5 D7 4B 34 69 44 1D 71 2D
       :           13 0E 4A FC 6E 50 1E 48 1A 2E 2F 88 57 CE 28 89
       :           5F 93 1E FF C3 A8 6C 58 0D 7D 85 E4 93 A4 7F 2B
       :           F7 EA 26 12 7F 99 5F 20 2E EA F5 E9 78 60 B9 E5
       :           C0
       :         }
       :       }
       :     }
       :   }
Answered By: Michael Fehr

I am running into the same problem but I believe that the public key is contained in the private key, which was generated by OpenSSL (this issue only seems to happen with P-521):

% openssl ec -in ecc-private-secp521r1.pem -noout -text
read EC key
Private-Key: (521 bit)
priv:
    00:84:d8:65:19:c6:b5:c5:f8:6d:e4:ca:03:60:38:
    c7:3f:fb:9c:33:9e:bc:6b:ba:24:15:ef:1c:d4:b1:
    bd:51:2c:4f:20:97:b4:16:b4:72:bb:04:5a:b8:e0:
    f3:af:e7:8d:d2:db:37:de:2c:20:7d:48:ca:a3:de:
    88:2e:ef:6c:48:16
pub:
    04:00:62:c8:d7:b2:06:b1:34:d4:e6:30:ff:66:fc:
    fc:24:e9:df:f0:ef:af:63:28:36:33:4a:32:2b:f6:
    66:65:3a:32:49:7f:5e:82:ca:9d:30:d5:60:cc:9e:
    ad:08:67:56:d2:c5:d2:bb:04:95:24:cd:e0:dc:71:
    91:17:54:5d:a3:9e:1e:01:15:a1:6a:96:4f:62:79:
    ff:c3:38:75:50:79:43:b5:1f:5a:cb:4b:74:d9:a7:
    d7:33:d0:fc:d5:1f:9d:26:40:4e:b4:16:f8:11:33:
    bb:d4:10:18:ab:e7:57:8f:e6:24:ea:af:32:70:75:
    cd:8c:43:ce:a1:39:1b:3e:d8:52:8f:74:75
ASN1 OID: secp521r1
NIST CURVE: P-521

Yet, I get the following error with a Python import:

    Traceback (most recent call last):
  File ".../Library/Python/3.9/lib/python/site-packages/Crypto/PublicKey/ECC.py", line 1753, in import_key
    result = _import_der(der_encoded, passphrase)
  File ".../Library/Python/3.9/lib/python/site-packages/Crypto/PublicKey/ECC.py", line 1468, in _import_der
    raise ValueError("Not an ECC DER key")
ValueError: Not an ECC DER key

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "...ecc-check-keys.py", line 48, in <module>
    eccKey = ECC.import_key(open("ecc-private-" + curve + "-" + str(i) + ".pem").read())
  File ".../Library/Python/3.9/lib/python/site-packages/Crypto/PublicKey/ECC.py", line 1757, in import_key
    raise ValueError("Invalid DER encoding inside the PEM file")
ValueError: Invalid DER encoding inside the PEM file

This only fails with P-521, P-384 works fine. I wonder if it’s because of the odd size of P-521 (not a multiple of 8). Any ideas?

Answered By: Quentin gct01