Python : Create ECC Keys from private and public key represented in raw bytes
Asked Answered
N

3

7

I have following ECC private and public key pairs:

Private key : 0x63bd3b01c5ce749d87f5f7481232a93540acdb0f7b5c014ecd9cd32b041d6f33

Public Key : 0x04017655e42a892cc71bccedcb1cd421d03530e1d7edb52cef143c5562c4c6f0129fa5a37738013e64a1ff0e6cb7068815a13000eb162cb7a0214dfcf3c8fa101c

Curve : SECP256R1

I want to load these keys in Python to perform signing operations. Can you please suggest possible steps?

(I am comfortable to use "openssl ec" tools if necessary.)

Northway answered 30/12, 2019 at 2:36 Comment(4)
Step one: generate a new keypair because you just posted this one on the internetCake
Make sure that the keys you post are not used anywhere in production. That's thin ice to walk~Speroni
Thanks a lot for the warning guys. This is a test key.Northway
The details of generating a key from the raw data depend on the respective library, e.g. in PyCryptodome the function construct can be used, in Cryptography derive_private_key and publicKey.Cobra
T
10

Here is a simple example (using python 3 + cryptography module) loading your key to sign/verify:

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.exceptions import InvalidSignature


private_value = 0x63bd3b01c5ce749d87f5f7481232a93540acdb0f7b5c014ecd9cd32b041d6f33
curve = ec.SECP256R1()
signature_algorithm = ec.ECDSA(hashes.SHA256())

# Make private and public keys from the private value + curve
priv_key = ec.derive_private_key(private_value, curve, default_backend())
pub_key = priv_key.public_key()
print('Private key: 0x%x' % priv_key.private_numbers().private_value)
print('Public point (Uncompressed): 0x%s' % pub_key.public_bytes(serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint).hex())

# Sign some data
data = b"this is some data to sign"
signature = priv_key.sign(data, signature_algorithm)
print('Signature: 0x%s' % signature.hex())

# Verify
try:
    pub_key.verify(signature, data, signature_algorithm)
    print('Verification OK')
except InvalidSignature:
    print('Verification failed')

This will display:

Private key: 0x63bd3b01c5ce749d87f5f7481232a93540acdb0f7b5c014ecd9cd32b041d6f33
Public point (Uncompressed): 0x04017655e42a892cc71bccedcb1cd421d03530e1d7edb52cef143c5562c4c6f0129fa5a37738013e64a1ff0e6cb7068815a13000eb162cb7a0214dfcf3c8fa101c
Signature: 0x304402200308ac7b7a56e7227d665d8f652d849935b4876c5ecef252ed9713c975b0a6280220696c134bb6e115b9ac18790c27009938f081bfaf063e547ce75bad3c9682890b
Verification OK
Tarr answered 31/12, 2019 at 0:37 Comment(3)
Thanks 'nicolas'. When I execute the sample I get different values for the Signature and point. But the signature verification succeeds. --- Private key: 0x63bd3b01c5ce749d87f5f7481232a93540acdb0f7b5c014ecd9cd32b041d6f33 Public point (Uncompressed): 0x04017655e42a892cc71bccedcb1cd421d03530e1d7edb52cef143c5562c4c6f0129fa5a37738013e64a1ff0e6cb7068815a13000eb162cb7a0214dfcf3c8fa101c Signature: 0x3044022005278dcb26e785ca59cdac78a9ac7ee0df19b4b5804f3250b00c22faf5d22a620220196269da58fa5ea3a2e365fe7af927723d2863a17cc8d756a6bb83fd4033348b Verification OK ---Northway
DSA and ECDSA by default require a random integer so if you run the same code twice, it is normal to get different signatures (but they are both correct).Tarr
DOn't you have the Open SSL error using this sample code?Carat
B
2

You can use the ECC.construct(**kwargs) call to construct keys from the respective integers.

I've shown below how to do this for an uncompressed point in hex and then bytes rather than as a number though. An uncompressed point is not a number in itself. So I haven't included the 0x in your question for these byte arrays.

The private key vector (usually denoted s or d, I prefer s for secret) is a number, but generally it will be transmitted using bytes as well (if it is ever transmitted, usually it is kept in place).

from Crypto.PublicKey import ECC

# --- ECC public key from "flat" uncompressed EC point representation ---

# lets assume that the input is binary, rather than an integer

uncompressedPointHex = "04017655e42a892cc71bccedcb1cd421d03530e1d7edb52cef143c5562c4c6f0129fa5a37738013e64a1ff0e6cb7068815a13000eb162cb7a0214dfcf3c8fa101c"
uncompressedPoint = bytes.fromhex(uncompressedPointHex)

# check if the point is uncompressed rather than compressed
# for compressed points ask a separate *question*

off = 0
if (uncompressedPoint[off] != 0x04):
    raise Exception("Not an uncompressed point")
off += 1

sizeBytes = (len(uncompressedPoint) - 1) // 2

xBin = uncompressedPoint[off:off + sizeBytes]
x = int.from_bytes(xBin, 'big', signed=False)
off += sizeBytes

yBin = uncompressedPoint[off:off + sizeBytes]
y = int.from_bytes(yBin, 'big', signed=False)
off += sizeBytes

if (off != len(uncompressedPoint)):
    raise Exception("Invalid format of uncompressed point")

# if you already have integers, this is all you need

publicKey = ECC.construct(curve="secp256r1", point_x=x, point_y=y)

# obviously just for testing the result

print(publicKey)

# --- ECC private key from "flat" uncompressed EC point representation ---

# lets assume that the input is binary, rather than an integer

sHex = "63bd3b01c5ce749d87f5f7481232a93540acdb0f7b5c014ecd9cd32b041d6f33"
sBin = bytes.fromhex(sHex)

# very straightforward conversion, as S is just there

s = int.from_bytes(sBin, 'big', signed=False)

# if you already have integers, this is all you need

privateKey = ECC.construct(curve="secp256r1", d=s)

# obviously just for testing the result

print(privateKey)

outputs

EccKey(curve='NIST P-256', 
    point_x=661393602013979783798470650260404653019684003375182707210783968552030760978,
    point_y=72210400889213969389982861398963807410315877398616325431902307461337204789276)
EccKey(curve='NIST P-256',
    point_x=661393602013979783798470650260404653019684003375182707210783968552030760978,
    point_y=72210400889213969389982861398963807410315877398616325431902307461337204789276,
    d=45113313355812346734724097146216873116458888764597604491161664272788312911667)

...slightly formatted with whitespace to show that the second key is indeed the private key containing d.

The x and y values can be calculated from d (point multiplication with the base point: d*G), which is why the private key can contain them without having them specified during construction.

Note that I've used Python 3, maybe some Python dev is able to convert it to Python 2 and include the result in this answer. The idea / calls should be similar after all.

Boaster answered 30/12, 2019 at 23:2 Comment(1)
Oh dear oh dear, that took way much more time than I would have expected. However, I finally got it done after setting up the PyCryptoDome package (first for Python, then for Python3, installing pip3, not noticing that I was using / instead of // and getting a rather obnoxious slicing error, then not understanding **kwarg which means a variable number of named arguments, using var++ which obviously did not work etc. etc.)Boaster
F
1

The ECDSA library can do this.

import ecdsa

skStr = "0x63bd3b01c5ce749d87f5f7481232a93540acdb0f7b5c014ecd9cd32b041d6f33"
skBytes = bytes.fromhex(skStr[2:])  # Skip "0x".
sk = ecdsa.SigningKey.from_string(skBytes, curve=ecdsa.NIST256p)

vkStr = "0x04017655e42a892cc71bccedcb1cd421d03530e1d7edb52cef143c5562c4c6f0129fa5a37738013e64a1ff0e6cb7068815a13000eb162cb7a0214dfcf3c8fa101c"
vkBytes = bytes.fromhex(vkStr[2:])  # Skip "0x".
if False:  # Expected to work, but memoryview[slice] != bytes:
    vk = ecdsa.VerifyingKey.from_string(vkBytes, curve=ecdsa.NIST256p)
else:  # Python 3.8 workaround
    vkPoint = ecdsa.VerifyingKey._from_raw_encoding(vkBytes[1:], curve=ecdsa.NIST256p)  # Skip b"\x04".
    vk = ecdsa.VerifyingKey.from_public_point(vkPoint, curve=ecdsa.NIST256p)
# or vk = sk.get_verifying_key()

Note that the curve of ecdsa.SECP256k1 does not work with the provided key data ("MalformedPointError: Point does not lie on the curve"), but ecdsa.NIST256p works fine.

Here is how you sign and verify a message:

message = b"Hello, world!"
signature = sk.sign(message)
print(f"Signature = 0x{signature.hex()}")
# Signature = 0x35b8d39a6655f8de13ebe9b30bbadd1c9dbf32ccfcb1c7ca106305214740b7dca652d59902eb7152c2e6e8bfc76872b803d1110defdf833bcb969a63beab6364
isSignatureValid = vk.verify(signature, message)
print(f"{isSignatureValid=}")
# isSignatureValid=True
Fraase answered 27/4, 2021 at 22:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.