如何在 Python 密码学中为 ECDSA (secp256k1) 生成更短的私钥

How to generate a shorter private key for ECDSA (secp256k1) in Python Cryptography

我正在尝试学习如何按照 this guide 创建比特币地址。如果向下滚动,第一步,即第 0 步,是拥有一个 256 位(64 十六进制)长的 ECDSA 密钥。我查看了 Python 密码学并使用下面的代码来测试生成密钥,但保存的密钥始终是一个长(180 个字符)base 64 字符串。

我已经尝试阅读文档并查看我在 Github 上调用的函数,但我看不到在哪里可以指定密钥的长度。在 this file 的第 216 行,它表示 secp256k1 的密钥大小默认为 256 位。这是否意味着我导出错误?

或者,我考虑过在 secp256k1(0x10xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4140 范围内生成一个长度为 64 个字符的随机十六进制字符串,但我不知道在哪里可以创建一个来自字符串或十六进制值的私钥实例

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-----

如果您没有 opaque 私钥(我认为这涉及专业硬件,所以不太可能),您可以通过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'

或按大端或小端顺序将其编码为 int.to_bytes() 字节(整数十六进制输出按大端顺序):

>>> 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'

所有这一切都有点令人费解,因为通常不需要操作 cryptography 模块,该模块通过数据结构与 OpenSSL 或其他密码学后端一起工作,这些数据结构将这些信息保存在库友好中,而不是 Python-友好的格式。

是的,您生成的密钥是 256 位长,您可以通过查看私钥的 .key_size 属性来验证这一点:

>>> key.key_size
256

DER 格式可能是另一条路径,因为那是机器可读的信息。传统的 OpenSSL 格式使得手动从 X.690 ASN.1 structure 中获取信息相对容易,无需安装 ASN.1 解析器,但这并不是万无一失的。你会寻找 04 20 字节序列(4 是一个八位字节字符串,20 十六进制表示它有 32 个字节长), 该值将是序列中的第二个元素第一个整数;这意味着私钥将始终从第 8 个字节开始:

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]

我不是 100% 确定这些断言是否成立,只是访问私人号码要简单得多。