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)
Advertisement
Answer
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 : } : } : } : }