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 key\n")
# 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)
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
: }
: }
: }
: }