Skip to content

第二章:把密码替换成密钥

ch02

第一章提到了传统的中心化服务会在数据库中存储用户的密码,比如游戏公司Q就是这样做的。但是这里有一个细节值得强调一下,那就是这些服务不应该以明文的方式存储密码。否则一旦数据库或者相关服务器被攻破,所有的用户名和密码都会泄漏。遗憾的是,在我们的现实世界中,曾经发生过多起这样的密码泄漏事故。

那么应该怎么避免这种情况呢?我们在第零章学过的哈希函数此时就派上用场了。简而言之,数据库中存储的不应该是密码明文,而是盐(Salt,其实就是一个随机数),以及密码加盐(简单拼接在一起即可)之后的哈希值。加盐是为了抵御彩虹表攻击(Rainbow Table Attack,查表攻击的一种),让密码破解变得更加困难。这里我们点到为止,具体细节下一小节还会继续介绍。

还有一个问题,用户凭什么相信某个公司真的没有用明文保存他的密码?答案是,没有任何凭据,只能无条件信任这家公司了。那有没有办法让用户彻底信服呢?有,彻底废弃密码,改用数字签名。这就是Q公司马上要做的事情,也就是我们在这一章要讨论的内容。

密码模式

我们先来看一下使用密码时,注册和登录流程分别是什么样的。本章暂不考虑转账流程,留到下一章再介绍。

注册。一,用户把用户名和密码发送给服务器。二,服务器确保用户名唯一,然后生成一个盐(随机数)。三,服务器用密码+盐算出一个哈希值。四,服务器在用户表中插入一条新的记录,并把用户名、盐和哈希值保存进去。注意密码是不会被存到数据库里的,简化后的注册流程如下图所示:

register1

登录。一,用户把用户名和密码发送给服务器。二,服务器从数据库中取出对应的盐和哈希值。三,服务器用密码+盐算出一个哈希值。四,服务器用算出的哈希值和数据库中记录的哈希值做对比,如果一致则登录成功,否则登录失败。简化后的登录流程如下图所示:

login1

签名模式

为了让用户彻底相信自己不会在数据库中用明文存储密码,Q公司决定完全废弃密码,转而使用数字签名。具体而言,它直接把用户表中的密码和盐这两个列都给删掉了,然后增加了一个新的列,用来存储用户的公钥。由于公钥本来就是可以公开的,所以把它用明文存在数据库里并没有什么不妥。用户表的这个改动如下图所示:

user2

把密码换成数字签名后,注册和登录流程都变复杂了。

注册。一,用户需要生成一对儿密钥,至于怎么生成后面会讲到。二,用户挑选一个用户名,并用自己的私钥对它进行签名。三,用户把用户名、公钥和签名一起发给服务器。四,服务器验证签名,并保证用户名唯一。五,如果这两点都没问题,就在用户表中插入一条记录,并把用户名和公钥保存进去。新的注册过程如下图所示:

register2

登录。一,用户使用自己的私钥对用户名签名。二,用户把用户名和签名发给服务器。三,服务器从数据库中查出对应的公钥。四,服务器验证签名,如果签名验证成功则登录成功,否则登录失败。新版的登录过程和注册差不多,只是用户不需要把公钥发送给服务器了,如下图所示:

login2

上面的新流程看起来挺好,但实际上隐藏着一个很大的问题,我们马上就会讨论。

重放攻击

细心的读者应该能发现这个问题,那就是每次登录用户都只对他的用户名进行签名。这有什么不对呢?想象一下,万一我们发给服务器的登录消息(用户名+签名)泄漏,那么黑客就会把这个消息保存起来。过一段时间以后,他直接把这个消息发给服务器就好了。由于签名是真实的,所以消息会通过服务器的检查。黑客顺利登录,然后就可以为所欲为。黑客这种复制消息然后发送的手段叫做重放攻击(Replay Attack)。

怎么解决这个问题呢?也简单,我们给登录消息增加一个版本号,让以前的消息过时就可以了。用户每次登录服务器的时候,都要把这个版本号增加一。为了让服务器记住某个用户上一次登录时使用的版本号,我们需要给用户表增加一列,如下图所示:

user3

要是你还没搞懂什么是重放攻击,不妨先回忆下:你有没有用过人脸识别登录某些App或服务?如果有人偷偷保存了你的照片、视频,想拿着这些现成的影像骗过登录系统,进而盗用你的账号、干坏事,这就是典型的重放攻击。那这些系统是怎么防住这种套路的呢?它会临时让你做个随机动作,比如摇摇头、眨眨眼,甚至张张嘴。只有你自己能实时完成这些操作,这样系统才会确认是你本人在登录。

版本号的作用与此类似。增加版本号之后,注册流程基本没有变化,只是服务器在插入新用户数据的时候,要把版本号初始化为零(5),如下图所示:

register3

用户在登录的时候,需要把用户名和当前版本号一起签名(1),然后发给服务器(2)。服务器收到消息之后,除了要检查签名以外,还要对比版本号,确认消息还没有被处理过(4)。如果两个检查都没问题,登录成功,服务器把用户的版本号加一写入数据库(5)。新的登录过程如下图所示:

login3

这里我们用到的“版本号”,在密码学与区块链领域的标准名称是“一次性随机数”(Number Used Once,简称Nonce),指特定上下文或操作中仅使用一次的值(形式可包括随机数、递增计数器等)。本书第四章区块链结构中的随机数、第七章账户数据中的交易次数,均是其具体应用。

生成私钥

切换到签名机制以后,我们解决了密码存在服务器容易泄漏的问题。但是又多出来四个新的问题:怎么生成私钥、怎么保管私钥、怎么使用私钥,以及私钥丢了怎么办?这一小节我们先来看第一个问题,剩下的三个问题下一小节再讨论。

到现在为止,我们都还没见过签名要用到的私钥,以及验证签名要用的公钥到底长什么样子。简单来说,私钥和公钥实际上都是普通的256比特整数(这么说并不是很严谨,但是这里简化一下无伤大雅)。密钥通常写成十六进制,比如我刚刚生成了一对儿私钥和公钥,它们看起来是下面这样的(私钥在前公钥在后):

0x07e6756ab80bdbe5fe28ba34e95c818684e09b2876401b53698e881487fc6d7d
0x11e3f59b47aef1d102683a52a859c41bdd2861039aceec9dea828deacca94d6e

在解释怎么生成以及保存公私钥对儿之前,先来说一下这两把钥匙之间的推导关系:通过私钥可以很容易计算出它所对应的公钥,但是反过来却非常的难。打个比方,我们可以轻易打碎一个玻璃瓶,但是很难把一堆玻璃碎片变成一个完整的瓶子。私钥和公钥之间的这种单向计算关系如下图所示:

key

既然我们已经知道通过私钥可以计算出对应的公钥,那么怎么生成公私钥对儿这件事就可以化简为怎么生成私钥。有了私钥,用它算出公钥就可以了。那么怎么生成一个私钥呢?说出来你可能不信,你只要拿一枚硬币,投掷256次,正面计为1,反面计为0,拼在一起,这样你就得到了一个256比特的随机整数,也就是一个私钥。问题解决。

当然啦,虽然说理论上是可行的,但是实际上没有人会通过投掷硬币的方式生成私钥,这也太麻烦了。有各种各样现成的工具来帮助我们生成私钥和公钥,我们只要执行一个命令,或者按一个(例如网页上的)按钮就可以生成一对儿新密钥。相较于怎么生成私钥,怎么保管已经生成的私钥更加棘手,我们下一节再来讨论。

密钥钱包

在使用密码的时候,我们还是比较容易能记住它们的。哪怕是忘了,通常也能找回。以游戏公司Q为例,我们的账号可能绑定了邮箱、手机号等。如果想不起来密码,就可以通过这些方式找回。密码用起来也方便,输入用户名密码,然后按回车键登录游戏,就这么简单。但是换成私钥可就没这几个优点了。

首先,私钥不太好记,试图把64个数字和字母背熟可能不是一个好主意,更何况我们有可能要背好几个这么长的数。其次,如果你的私钥丢了,就真的丢了。仅通过公钥,任何人都无法帮你恢复私钥。最后,我们在登录游戏的时候是需要用私钥对消息签名的,不能直接把私钥发送给服务器。

那么怎么解决这几个问题呢?答案是使用密钥钱包(简称钱包)。实际上大部分钱包也提供了生成私钥的功能,所以它一下子把管理(生成、保存)私钥和使用密钥(生成签名)的问题都给解决了。虽然说名字叫做钱包(Wallet),可它里面放的并不是钱而是密钥,所以它更像我们现实生活中的钥匙串(Keyring)。可是钱包这个名字已经约定俗成了,所以我们也只好沿用它了。钱包和私钥的关系如下图所示:

wallet

那么由谁来开发钱包软件呢?这个你完全不用担心。既然Q公司打算改造它的服务,那么很可能你手机里安装的某款它发行的游戏已经内置了钱包功能。生成私钥、登录游戏都可以一键完成。注意,你的私钥是由游戏App保存在你自己的手机里的,并不会被上传到游戏公司的服务器上。那么如果你的手机丢了,或者坏了怎么办?是不是相当于你的私钥也给丢了?是这样的。世界就是这样,解决一个旧问题总是会带来更多的新问题。

如果你担心手机丢了或者坏了,可以备份一下你的私钥。比如Q公司的游戏App就提供了这样的功能,你点某个按钮它就会展示你的私钥(十六进制)。你可以把这个私钥记在脑子里(虽然我不建议你这么干),这就叫做脑钱包。或者你把它抄在纸上,放到一个安全的地方以备不测,这就叫做纸钱包。

剧本是这样发展的,Q公司持续推出多款新游戏,市场份额逐渐提高。而Q公司的这次改造也非常成功,大量的用户把登录模式切换成了数字签名。由于用户量庞大,市面上出现了多款第三方钱包软件,专门替用户管理私钥。这些软件有的是桌面应用,有的是手机应用,还有的是浏览器(例如Chrome或Firefox)插件。甚至还出现了专业的硬件钱包,将用户的私钥存储在离线芯片中,隔绝网络黑客攻击与恶意程序窃取风险,成为重度玩家和工作室保障账号安全的首选工具。

不仅如此,最早迁移到签名模式的一些早期用户还成立了专门的公司,提供私钥托管服务。如果你想体验Q公司的签名模式又不想自己管理私钥,可以使用这些中心化的托管服务。你的私钥存在托管服务的数据库中。当你登录游戏的时候,客户端会跳转到托管服务的页面,请求签名。然后你在页面上输入密码,点击确定,托管服务返回签名。最后游戏客户端完成登录,你开始愉快地玩游戏。

总之,钱包的类型还是比较丰富的,根据不同的需求有不同的选择。以上介绍的钱包类型在区块链生态中也都有明确对应的应用形式,下面这张表列出了一些例子:

由谁保管是否联网钱包类型例子
自己保管✘(冷钱包)脑钱包(记在心里)
纸钱包(抄在纸上)
硬件钱包Ledger、Trezor
✔(热钱包)电脑AppElectrum、Sparrow
手机AppTrust Wallet
浏览器插件Metamask
他人托管交易所AppBinance、Coinbase

本章小结

为了解决用户密码泄漏的问题,Q公司引入了数字签名模式。如果用户信任Q公司,那么可以继续使用密码模式,否则可以使用签名模式。本章首先回顾了密码模式下的注册和登录流程,然后介绍了签名模式下新的注册和登录流程,包括如何避免重放攻击。然后讨论了签名模式带来的一系列新问题,例如如何生成和保存私钥,以及如何使用私钥。通过解决这些问题,本章在结尾重点介绍了钱包的概念。

从密码到密钥,Q公司迈出了去中心化道路上的第一步。虽然Q公司通过升级注册和登录方式大获成功,但是也遇到了新的挑战,而破解之道依然藏在数字签名里。道路是曲折的,但前途是光明的。我们会在下一章看到Q公司遇到了什么样的挑战,以及它怎么化解这个难题。

本书以 CC0 1.0 协议发布,可自由使用