这篇 blog 的内容原本是去年 10 月写的,当时正在看《24小时反恐》,脑子里涌现出无数古怪的想法,觉得这个世界到处都是特工,什么通讯手段都不可靠。在我们还没有能力获得量子加密需要的硬件前,能有限依靠的恐怕只有数学能保证的加密技术。
当时那篇 blog 写了一大篇,自己都觉得太过于天马行空的乱扯,就没有公开。
不过今天开周会,我们又提到游戏 client 提交密码的安全性问题,指派了一个同事最近在这方面做些工作,这里也写点以前研究的东西,留点记录吧。
简单说,我们应该避免在一次登陆过程中从互联网连接中传递明文的用户名密码信息。这是一个起码的要求,但是我们以往的产品做的并不好。很多时候都是伪加密。就是 client 用个私有算法将密码信息编码后传送,再由 server 用相同的算法还原。
这个安全性极度依赖 client 的程序不被逆向工程。一旦有人完全逆向工程后,只需要他监听到通讯,就可以还原出用户的密码。
物理上防止通讯被监听的技术在可见的时间内几乎不可能被大众使用,我们现在即使能保证自己的 client 机器没有被人动过手脚,还是无法知道自己的通讯数据是否被监听。只能寄希望于数学的加密技术了。
SSL 就是干这个的,但是由于种种原因,暂时我们还不能在游戏 client/server 中推行使用。那么,现在是不是可以抽出一些东西来,自己先在程序中实现出来用着。其实,优先要做到的,无非是安全的让用户提交用户名密码而已。
最著名的算法莫过于 RSA 。我想任何一个理工科跟计算机沾点边的大学生都应该了解那么一点。
知道 RSA 算法大约是在高考前夕。当时读到了 Gates 先生写的一本书《未来之路》。让我尤为感兴趣的是其中介绍的非对称公私匙加密算法。进了大学后,当拥有很多的时间时,我去校图书馆翻阅了一些论文,了解了最著名的 RSA 。
简单来说,RSA 算法可以看成一个带锁的箱子。只有箱子制造者可以打开这把锁。而箱子上有条缝,别人没有钥匙也可以往里面塞东西。
如果 A 和 B 想交换保密信息的话,那么由 A 做一个 RSA 箱子交给 B 。B 把东西塞进箱子送还给 A 。这时,A 就可以自己把箱子打开,拿到 B 给他的东西了。
RSA 的问题在于计算速度。RSA 需要做大数的幂运算,这需要消耗大量的计算资源。所以直接对信息做 RSA 加密不太适合高带宽的通讯。继续上面那个直观的比喻:RSA 固然是个不错的箱子,但无论是打开它还是向里面塞东西,都颇费周折。所以,作为 B ,通常是配一对普通锁的钥匙。把钥匙而不是东西本身塞进 RSA 箱子。然后再用轻巧(但同样坚固)的锁来运送货物。
现在的问题是,直接拿 RSA 来加密用户登陆信息是否合适?
看起来是满不错的,用户登陆无非一个用户名一个密码,符合上面轻巧的定义。如果我们不要求特别高的安全性是完全可行的。不过这里我们想讨论一下别的算法方案。
关于 RSA 的问题。
如果有足够的计算能力,和足够的时间,理论上我们可以从 RSA 的 public key 中提取出 private key 来。所以,基于 RSA 的加密系统都会在生成 key pair 时给它签上一个过期时间。
假设我们每次通讯都用 RSA 加密当次通讯用的随机 key ,再用这个随机 key 做传统对称加密(比如 DES),那么在一定时间内,这些通讯数据我们都可以认为是安全的。
可是,如果有不怀好意的人从一开始就监听了你的所有通讯数据流并记录下来会怎样?虽然他一开始并没有能力解密里面的数据,但是他可以昼夜不息的去分解那个大素数,直到有一天提取出你的 private key 为止。即使你在 key pairs 过期之前更换了新的钥匙,但是,你曾经的所有通讯数据都会在老的 key 被解密那天同时变成明文。
而且,如果你是用私人途径发布 public key 来防止恶意监听者拿到可供分析的材料。那么你用这个 key 次数越多就越容易暴露。
怎么办?如果想避免大量历史信息同时被解密,难道我们每隔几次通讯就换一对钥匙吗?RSA 的钥匙生成代价可是很大的。
伟大的数学家们自然有好方法来解决这个问题,其中最著名的某过于 Diffie Hellman 密匙交换协议。
简单说来 Diffie Hellman 协议可以干这么一件事:A 拿到一个神奇的 Diffie Hellman 箱子,随便找把锁(选择一个随机数)锁起来,发给 B 。这个箱子的神奇之处在于,一旦上两次锁,它内部的机关就会根据这两把锁在内部制造出一个随机的钥匙。只要每次上的锁不一样,制造出来的钥匙就不一样。当 B 收到这个箱子后,加上第二把锁。打开箱子,取到随机钥匙配上一把,然后把箱子送还给 A 。
这个时候,A 和 B 都是箱子的主人。而当 A 也打开箱子,就取到了 B 已有副本的那把钥匙。今后,A 和 B 就可以用这对相同的钥匙来加密传送信息了。
和 RSA 箱子不同,每个 RSA 箱子和锁是一体的。而 Diffie Hellman 箱子可以配许多把普通的锁。每一个独特的 Diffie Hellman 箱子的制造成本虽然相对比较高(要生成素数的原根还是很麻烦的)。但因为锁上它的锁成本却很低。每次 A 和 B 想交换钥匙,都可以换新的锁。这样,每次通讯都可以看成是独立的,一组通讯被破解,并不影响其它通讯的安全性。只是 RSA 箱子虽然繁杂,理论上却可以装任何东西。Diffie Hellman 箱子固然神奇,但它却放不了指定的物品,只能自己生成钥匙罢了。
btw, 以上的比喻只是方便理解,可能并不太准确。有兴趣想了解的朋友可以自己去 google 资料。
我们很容易想到, Diffie Hellman 协议也有天生的缺陷。随机生成的密码不能携带双方的身份信息(因为不能人为指定)。这样容易被中间人攻击。如果需要,我们就得进一步改进它。联合使用 DH 和 RSA 可能是个好主意,今天就不展开讨论了。也有对 DH 改进的协议如 OAKLEY 协议 用来解决用户身份认证的问题。
前两年刚玩 palm 手机时有个有趣的想法,就是做一个加密短信发送软件。不用太复杂,用类似 DES 的对称加密算法就可以了。如果两个朋友有见面的机会,用红外传输约定一个密钥,以后可以用这个密钥加密发送短信。由于密钥没通过无线网络传输,仅限于面对面红外约定,我们可以认为密码交换是安全的。
如果简单的每次都用那个约定的密钥加密,一旦有一天有一方的手机本身落入“老大哥”手里,那么由于以前所有的加密通讯都可能被记录,也就意味着所有以往的秘密都被公开了。这对另一方非常不利。
我想了个简单的方法避免陷入这种尴尬的局面。那就是每次加密短信发完后,都将密钥 hash ( md5 ) 一次生成下一个版本。发送加密短信时,将密钥版本号明文附在信息前面,防止中途丢失信息导致的密钥不同步。由于 hash 函数的不可逆性,我们就可以建立起一个有相对隐私的 p2p 短信平台了 :)