在加密货币中,私钥允许用户访问其钱包。持有私钥的人完全控制该钱包中数字货币。出于这个原因,你应该保守秘密。如果你真的想自己生成密钥,那么以安全的方式生成密钥是有意义的。在这里,我将介绍私钥,并向你展示如何使用各种加密函数生成自己的密钥。我将在Python中提供算法和代码的描述。
我需要生成私钥吗?
大多数时候你没有。例如,如果你使用Coinbase或Blockchain.info等网络钱包,他们会为你创建和管理私钥。交易所也是如此。
移动和桌面钱包通常也会为你生成私钥,但他们可以选择使用你自己的私钥创建钱包。
那么为什么要生成呢?以下是我的原因:
- 你想确保没有人知道密钥。
- 你只想了解有关加密和随机数生成(RNG)的更多信息。
在此推荐小编创建的Python学习交流群:835017344,这里是python学习者聚集地,有大牛答疑,有资源共享!有想学习python编程的,或是转行,或是大学生,还有工作中想提升自己能力的,正在学习的小伙伴欢迎加入学习。
什么是私钥?
形式上,比特币(以及许多其他加密货币)的私钥是一系列32字节。现在,有很多方法可以记录这些字节。它可以是256个1和0(32*8=256)或100个骰子所组成的字符串。它可以是二进制字符串,Base64字符串,WIF密钥,助记符短语,或最后是十六进制字符串。出于我们的目的,我们将使用64个字符长的十六进制字符串。
为什么正好是32字节?好问题!你可以看到,要从私有密钥创建公钥,比特币使用
ECDSA
或椭圆曲线数字签名算法。更具体地说,它使用一个称为
secp256k1
的特定曲线。
现在,该曲线具有256位的量级,以256位作为输入,并输出256位整数。256位正好是32个字节。因此,换句话说,我们需要32字节的数据来提供给这种曲线算法。
私钥还有一个额外的要求。因为我们使用ECDSA,所以关键应该是正数,并且应该小于曲线的顺序。secp256k1的顺序是
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
,它非常大:几乎任何32字节的数字都会比它小。
简单粗暴的方法
那么,我们如何生成一个32字节的整数? 首先想到的是只使用你选择的语言的RNG库。Python甚至提供了一种生成足够位的友好方式:
import random
bits = random.getrandbits(256)
30848827712021293731208415302456569301499384654877289245795786476741155372082
bits_hex = hex(bits)
0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32
private_key = bits_hex[2:]
4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32
看起来很好,但实际上,它不是。你看,普通的RNG库不适用于加密,因为它们不是很安全。它们根据种子生成数字,默认情况下,种子是当前时间。这样,如果你大致知道我上面生成的那些位,你需要做的就是暴力破解一些变种。
生成私钥时,你希望非常安全。请记住,如果有人学习私钥,他们可以轻松地从相应的钱包中窃取所有硬币,而你没有机会将其取回。
所以让我们尝试更安全地做到这一点。
密码学上强大的RNG
除了标准的RNG方法,编程语言通常还提供专门用于加密操作的RNG。这种方法通常更加安全,因为它直接从操作系统中提取熵。这种RNG的结果很难再现。你不能通过知道生成时间或种子来做到这一点,因为没有种子。好吧,至少用户不输入种子;相反,它是由程序创建的。
在Python中,密码强的RNG在
secrets
模块中实现。让我们修改上面的代码,使私钥生成安全!
import secrets
bits = secrets.randbits(256)
46518555179467323509970270980993648640987722172281263586388328188640792550961
bits_hex = hex(bits)
0x66d891b5ed7f51e5044be6a7ebe4e2eae32b960f5aa0883f7cc0ce4fd6921e31
private_key = bits_hex[2:]
66d891b5ed7f51e5044be6a7ebe4e2eae32b960f5aa0883f7cc0ce4fd6921e31
这是惊人的。我敢打赌,即使访问我的电脑,你也无法重现这一点。但我们可以更深入了吗?
专业的网站
有些网站会为你生成随机数。我们在这里只考虑两个。一个是random.org ,一个众所周知的通用随机数发生器。另一个是bitaddress.org ,专门用于比特币私钥生成。
random.org可以帮助我们生成密钥吗? 当然,因为他们有生成随机字节的服务 。但是这里出现了两个问题。Random.org声称是一个真正的随机发生器,但你能相信吗? 你能确定它确实是随机的吗? 你能确定所有者不记录所有代的结果,特别是那些看起来像私钥的结果吗? 答案取决于你。哦,你不能在本地运行它,这是一个额外的问题。此方法不是100%安全。
现在,bitaddress.org是一个完全不同的故事。它是开源的,所以你可以看到它的内部代码。它是客户端,因此即使没有Internet连接,你也可以下载并在本地运行它。
那么它是怎样工作的? 它使用你——是的,你自己——作为熵的来源。它会要求你移动鼠标或按随机键。你做得足够长,使得重现结果变得不可行。
你是否有兴趣了解bitaddress.org的工作原理?出于学习目的,我们将查看其代码并尝试在Python中重现它。
快速说明:bitaddress.org为你提供压缩WIF格式的私钥,该格式接近我们之前讨论过的WIF格式。出于我们的目的,我们将使算法返回一个十六进制字符串,以便我们以后可以使用它来生成公钥。
Bitaddress:具体细节
Bitaddress以两种形式创建熵:通过鼠标移动和按键压力。我们将讨论两者,但我们将重点关注按键,因为很难在Python lib中实现鼠标跟踪。我们希望最终用户在我们有足够的熵之前键入按钮,然后我们将生成一个密钥。
Bitaddress做了三件事。它初始化字节数组,试图从你的计算机获得尽可能多的熵,它用用户输入填充数组,然后生成一个私钥。
Bitaddress使用256字节数组来存储熵。这个数组是循环重写的,所以当第一次填充数组时,指针变为零,并且填充过程再次开始。
程序从window.crypto启动一个256字节的数组。然后,它写入时间戳以获得额外的4个字节的熵。最后,它获取的数据包括屏幕大小,时区,浏览器插件信息,区域设置等。这给了它另外6个字节。
初始化之后,程序不断等待用户输入以重写初始字节。当用户移动光标时,程序会写入光标的位置。当用户按下按钮时,程序会写入按下的按钮的字符代码。
最后,bitaddress使用累积的熵来生成私钥。它需要生成32个字节。对于此任务,bitaddress使用名为ARC4的RNG算法。程序用当前时间初始化ARC4并收集熵,然后逐个获取32次字节。
这完全是对程序运作方式的过度简化,但我希望你能得到这个想法。你可以在 Github 上详细查看算法。
自己动手吧
为了我们的目的,我们将构建一个更简单的bitaddress版本。首先,我们不会收集有关用户机器和位置的数据。其次,我们将仅通过文本输入熵,因为使用Python脚本持续接收鼠标位置非常具有挑战性(如果你想这样做,请检查 PyAutoGUI )。
这将我们带到了我们的生成器库的正式规范。首先,它将使用加密RNG初始化一个字节数组,然后它将填充时间戳,最后它将填充用户创建的字符串。种子池填满后,库将让开发人员创建一个密钥。实际上,他们将能够创建任意数量的私钥,所有私钥都由收集的熵保护。
初始化池
这里我们从加密RNG和时间戳中放入一些字节。
__seed_int
和
__seed_byte
是两个将熵插入池数组的辅助方法。请注意,我们使用
secrets
。
def __init_pool(self):
for i in range(self.POOL_SIZE):
random_byte = secrets.randbits(8)
self.__seed_byte(random_byte)
time_int = int(time.time())
self.__seed_int(time_int)
def __seed_int(self, n):
self.__seed_byte(n)
self.__seed_byte(n >> 8)
self.__seed_byte(n >> 16)
self.__seed_byte(n >> 24)
def __seed_byte(self, n):
self.pool[self.pool_pointer] ^= n & 255
self.pool_pointer += 1
if self.pool_pointer >= self.POOL_SIZE:
self.pool_pointer = 0
输入种子
这里我们首先放置一个时间戳,然后输入字符串。
def seed_input(self, str_input):
time_int = int(time.time())
self.__seed_int(time_int)
for char in str_input:
char_code = ord(char)
self.__seed_byte(char_code)
生成私钥
这部分可能看起来很难,但实际上非常简单。
首先,我们需要使用我们的池生成32字节的数字。不幸的是,我们不能只创建自己的
random
对象,只能用于密钥生成。相反,有一个共享对象,由在一个脚本中运行的任何代码使用。
这对我们意味着什么?
这意味着在每个时刻,代码中的任何地方,一个简单的
random.seed(0)
都可以破坏我们收集的所有熵。我们不希望这样。值得庆幸的是,Python提供了
getstate
和
setstate
方法。因此,为了在每次生成密钥时保存我们的熵,我们记住我们停下来的状态,并在下次我们想要创建密钥时设置它。
其次,我们只确保我们的键在范围内(1,
CURVE_ORDER
)。这是所有ECDSA私钥的要求。
CURVE_ORDER
是secp256k1曲线的顺序,它是
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
。
最后,为方便起见,我们转换为十六进制,并剥离
'0x'
部分。
def generate_key(self):
big_int = self.__generate_big_int()
big_int = big_int % (self.CURVE_ORDER — 1) # key < curve order
big_int = big_int + 1 # key > 0
key = hex(big_int)[2:]
return key
def __generate_big_int(self):
if self.prng_state is None:
seed = int.from_bytes(self.pool, byteorder=’big’, signed=False)
random.seed(seed)
self.prng_state = random.getstate()
random.setstate(self.prng_state)
big_int = random.getrandbits(self.KEY_BYTES * 8)
self.prng_state = random.getstate()
return big_int
行动
我们试着使用这个库。实际上,它非常简单:你可以使用三行代码生成私钥!
kg = KeyGenerator()
kg.seed_input(‘Truly random string. I rolled a dice and got 4.’)
kg.generate_key()
60cf347dbc59d31c1358c8e5cf5e45b822ab85b79cb32a9f3d98184779a9efc2
你可以自己看看。密钥是随机的,完全有效。而且,每次运行此代码时,都会得到不同的结果。
结论
如你所见,有很多方法可以生成私钥。它们的简单性和安全性不同。
生成私钥只是第一步。下一步是提取可用于接收付款的公钥和钱包地址。