最近有一个挺流行的段子:
https://twitter.com/o_lll/status/848376854885023745
清明节了,大家给程序员烧纸的时候一定要注意,公钥和私钥要分开烧
然而,从安全的角度上来讲,这样的做法并不靠谱。不但烧私钥不靠谱,烧公钥同样也不靠谱,为什么呢?
对称加密
加密方法是用来保障通信安全的。这里就要扯到每本密码课本里都会提到的那个例子:Alice有一个重大的消息P(明文)想告诉Bob,然而邪恶的Eve在窃听他们的通信。Alice要怎么做才能既把消息传递给Bob,又不让Eve知道呢?
为了解决这个问题,密码应运而生,Alice和Bob可以事前约定一个加密方法E和密钥K。这样子,Alice在发送之前,可以使用密钥对信息进行加密:
[latex]C = E_K(P)[/latex]
之后,Alice把加密过的信息C(密文)发送给Bob,后者再使用同样的密钥K对信息进行解密:
[latex]P = D_K(C)[/latex]
这里的加密方法E可以是“把所有字母按字母表向后推K个”这种简单的凯撒密码,也可以是复杂高精尖的加密算法,具体使用什么样的加密方法,要根据Eve的愚蠢程度和信息P的重要程度而定。只要Eve猜不到K,就算他知道C和E也无济于事。
我们把这样的加密方法称为“对称加密”,因为Alice加密和Bob解密时,使用的是相同的密钥K。如果用邮局通信来类比,这样的加密方法类似于,Alice寄给Bob一个上锁的小盒子,并且把钥匙事先给Bob配一把,那Bob收到盒子,用钥匙解开锁就好了。
非对称加密
对称加密的问题在于,它要求Alice和Bob两个人事先约定好密钥K。假设Alice和Bob之间并没有安全的通信通道,Alice又该怎么把密钥K传递给Bob呢?
回到前面的邮寄的例子来,假设那把锁打开和上锁都需要同一把钥匙,一种解决方法是这样的:
Alice把信件放到盒子里,然后用锁a把这个盒子锁上,寄给Bob,自己保留锁a的钥匙。Bob收到信件之后,再用锁b把这个盒子锁上,寄回给Alice,自己保留锁b的钥匙。Alice收到盒子,解开锁a,把盒子寄回给b,这样盒子上就只剩下一把锁了,于是b用自己的钥匙解开了这把锁,拿到信件。(这种方法的思路类似于Diffie-Hellman密钥交换协议,但是并不完全相同。)
当然,如果那把锁锁上并不需要钥匙,打开才需要钥匙,这个问题就会变得更简单一些:Bob寄给Alice一把打开的锁,自己保留钥匙,然后Alice用这把锁把信件锁在盒子里,寄回给Bob。Bob用自己的钥匙打开这把锁,就可以得到信件了。
这个类比对我们的启示在于,加密和解密未必需要同样的密钥——加密的时候,你使用的是打开的锁,解密的时候则使用的是留在自己手里的钥匙。所以,如果我们能够找到这样的一种密码E,让加密和解密时候使用的密钥不一样,那就算加密的密钥e被Eve知道了也没有关系:只要他不知道解密的密钥d就好。我们把这样的密码称为公钥密码,把e称为公钥,把d称为私钥。有了这样的密码E之后,Bob只要公开自己的公钥e就好了:Alice可以用e加密明文P,然后把加密得到的密文C传输给Bob,Bob用自己的d解密密文C,得到明文P。
这样的密码有很多,最具有代表性的就是RSA算法,它利用了因数分解的不可逆性:把两个素数相乘很容易,但是分解一个大整数被认为是非常难的问题。(从理论上来说,量子计算机可以非常快的分解质因数,但是它离实用化还非常远)。
公钥密码的另外一个用途是数字签名:粗略的说,假设Bob公开了解密的密钥d而非加密的密钥e,然后用e加密了一段信息P,得到密文C。因为C只有拥有私钥e的人才能产生,任何人都可以用公钥解密C,来验证这段信息的确是Bob产生的。
公钥密码今天被广泛的应用在各种场合。人们使用OpenPGP协议(GnuPG是一个流行的实现)来加密电子邮件和签名,网站使用HTTPS协议来证明自己的身份和加密访问的信息,SSH协议也使用公钥密码来确保服务器不会被非法用户访问,等等不一而足。
中间人攻击和密钥认证
然而,这样的系统并不是完全安全的。我们再回到刚刚那个邮寄锁的类比上来:
假设Bob通过邮局把一把打开的锁寄给Alice,而Eve在邮局工作,那他可以截取Bob的邮件,用自己的一把锁替换掉Bob的锁,然后照常把邮件寄送给Alice。不知情的Alice用这把锁锁上了自己的信件,然后把盒子通过邮局寄出。Eve再次截获了这封信,用自己的钥匙解开那把锁,阅读信件内容,然后再用Bob的锁锁上这个盒子。这样子,Eve截获了通信的内容(甚至可以更改信件的内容),而Alice和Bob都不知情。类似的方法也可以被应用到公钥密码当中,这就是所谓的中间人(Man-in-the-middle)攻击。
要防止这样的攻击,关键在于让Alice加密前有办法验证“这的确是Bob的锁(公钥)”。有几种方法可以做到这一点:
- Bob可以当面把自己公钥(或者公钥的特征)交给Alice,这样Alice就可以验证这的确是Bob的公钥。
- 如果某个双方都信任的权威机构对Bob的公钥进行了数字签名,那只要Alice可以确认这个权威机构的身份,也就可以确认Bob的公钥的确是真的。
- 或者,如果大多数认识Bob的人都认可Bob的公钥是真的,那Alice也可以相信Bob的公钥是真的。
HTTPS协议使用了第二种方法:人们在全球建立了若干根证书颁发机构(CA),如果某个网站的身份得到了他们的直接或者间接确认,那它的身份也就得到了确认。绝大多数的操作系统和浏览器都内置了若干权威的CA的证书。当你浏览这篇文章的时候,你会看到浏览器地址栏里的锁的图标,点开它,你会看到这个博客的身份是由哪家公司确认的。如果这个信任链没办法建立起来,你就会看到浏览器报错——所以呢,请别随便忽略下图这种错误信息,也别随便安装不靠谱的根证书。
OpenPGP协议使用了第一种和第三种方法:在导入一个人的公钥的时候,你可以看到这个人的公钥的指纹(特征),也可以看到哪些人对这个公钥进行了认可和签名。最近流行的keybase.io网站,则是利用一个人难以伪造的公开网络身份(Twitter,Github),来验证一个人公钥的真假。
说到这里,我们终于可以解答开头的那个段子错在哪里了:
- 私钥是绝对不能烧的——私钥永远不应该被分享,永远不应该。
- 烧公钥容易引发中间人攻击,而且也没什么用——你需要程序员的公钥才能给他发送加密信息。
其实呢,大概怎么加密也不重要吧——爱本身才是最可靠的信道呢。
最后,如果有需要,你可以在https://hoppinglife.com/public.asc和https://keybase.io/hoppinglife找到我的GPG公钥。
Also published on Medium.