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

muc*_*le6 5 python cryptography bitcoin python-cryptography

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

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

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

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)
Run Code Online (Sandbox Code Playgroud)

私有密钥.pem

-----BEGIN PRIVATE KEY-----
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgQGh8om7IuKSTW637ZQug
SZQHUTv/yQzmM+KxGi1bg0ehRANCAATALLpDeKtfHxEnrgazJUu2z2/esSfzF5bj
Z4B/IBBB9uYHyMtjY8hS926bpXiWql7y7MMZXDSDD/zYWELuJZ1U
-----END PRIVATE KEY-----
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 6

如果您没有不透明的私钥(我认为这会涉及专业硬件,所以不太可能),您可以通过私钥对象的key.private_numbers()方法访问私人号码信息,此时您可以访问该值本身为整数;该.private_numbers()方法生成一个带有属性EllipticCurvePrivateNumbers的对象,即 Python 。使用以下命令将该值格式化为 64 个字符的零填充十六进制:.private_valueintformat()

>>> 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'
Run Code Online (Sandbox Code Playgroud)

或以大端或小端顺序将其编码为字节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'
Run Code Online (Sandbox Code Playgroud)

所有这些都有点复杂,因为通常不需要操作该cryptography模块,该模块通过数据结构与 OpenSSL 或其他加密后端一起使用,这些数据结构将这些信息保存为库友好的格式,而不是 Python 友好的格式。

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

>>> key.key_size
256
Run Code Online (Sandbox Code Playgroud)

DER 格式可能是另一个路径,因为那是机器可读的信息。传统的 OpenSSL 格式使得手动从X.690 ASN.1 结构中获取信息相对容易,无需安装 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]
Run Code Online (Sandbox Code Playgroud)

然而,我并不能 100% 确定这些断言是否成立,而且仅访问私人号码要简单得多。