2

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)
Michael Fehr
  • 5,827
  • 2
  • 19
  • 40

1 Answers1

1

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
       :         }
       :       }
       :     }
       :   }
Michael Fehr
  • 5,827
  • 2
  • 19
  • 40