Reputation: 655
I'm trying to learn how to create a bitcoin address by following this guide. If you scroll down, the first step, step 0, is to have a 256 bit (64 hex) long ECDSA key. I looked into Python Cryptography and am using the code below to test generating keys, but the saved key is always a long (180 characters) base 64 string.
I've tried to read the docs and look at the functions I'm calling on Github but I don't see where I can specify how long the key should be. On line 216 of this file, it says that the key size for secp256k1 is 256 bits by default. Does that mean I'm exporting it wrong?
Alternatively, I've considered generating a random hex string 64 characters long within the range of secp256k1,( 0x1
to 0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4140
), but I don't see where I can create a private key instance from a string or hex value
gentest.py
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.serialization import load_pem_private_key
def gen_key():
private_key = ec.generate_private_key(
ec.SECP256K1(), default_backend()
)
return private_key
def save_key(pk, filename):
pem = pk.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
with open(filename, 'wb') as pem_out:
pem_out.write(pem)
def load_key(filename):
with open(filename, 'rb') as pem_in:
pemlines = pem_in.read()
private_key = load_pem_private_key(pemlines, None, default_backend())
return private_key
if __name__ == '__main__':
pk = gen_key()
filename = 'privkey.pem'
save_key(pk, filename)
pk2 = load_key(filename)
privkey.pem
-----BEGIN PRIVATE KEY-----
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgQGh8om7IuKSTW637ZQug
SZQHUTv/yQzmM+KxGi1bg0ehRANCAATALLpDeKtfHxEnrgazJUu2z2/esSfzF5bj
Z4B/IBBB9uYHyMtjY8hS926bpXiWql7y7MMZXDSDD/zYWELuJZ1U
-----END PRIVATE KEY-----
Upvotes: 5
Views: 4301
Reputation: 1121168
If you do not have an opaque private key (I think that'd involve specialist hardware, so not likely), you can get access to the private numbers information via the key.private_numbers()
method of the private key object, at which point you can access the value itself as an integer number; the .private_numbers()
method produces a EllipticCurvePrivateNumbers
object with a .private_value
attribute, a Python int
. Format that value as a 64-character zero-padded hex with format()
:
>>> key = gen_key()
>>> key.private_numbers()
<cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateNumbers object at 0x110a6b828>
>>> key.private_numbers().private_value
1704732612341385685752055250212403073347894734334856205449544619169914419683
>>> format(key.private_numbers().private_value, '064x')
'03c4d82ee8e4c9d245f5a5ceae513569fb5693a0c3cca223b198c6944521f9e3'
or encode it to bytes with int.to_bytes()
in big or little endian order (the integer hex output is in big-endian order):
>>> key.private_numbers().private_value.to_bytes(32, 'big')
b'\x03\xc4\xd8.\xe8\xe4\xc9\xd2E\xf5\xa5\xce\xaeQ5i\xfbV\x93\xa0\xc3\xcc\xa2#\xb1\x98\xc6\x94E!\xf9\xe3'
>>> key.private_numbers().private_value.to_bytes(32, 'big').hex()
'03c4d82ee8e4c9d245f5a5ceae513569fb5693a0c3cca223b198c6944521f9e3'
All this is a little convoluted because that's not normally needed to operate the cryptography
module, which works with OpenSSL or other cryptography backends via data structures that keep this information in library-friendly, not Python-friendly formats.
And yes, the key you produce is 256 bits long, you can verify this by looking at the .key_size
attribute of the private key:
>>> key.key_size
256
The DER format could be another path, because that's machine-readable information. The traditional OpenSSL format makes it relatively easy to fish the info out of the X.690 ASN.1 structure manually, without installing a ASN.1 parser, but this is not exactly fool-proof. You'd look for the 04 20
byte sequence (4 is an octet string, 20 hex means it's 32 bytes long), and the value would be the second element in a sequence with the first an integer; this means the private key will always start at the 8th byte:
der_bytes = key.private_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption())
assert der_bytes[5:7] == b'\x04\x20'
key_bytes = der_bytes[7:39]
I'm not 100% certain if those assertions hold however, and just accessing the private numbers is much simpler.
Upvotes: 6