« 版本控制系统再考察 | 返回首页 | 辟谣 »

安全的提交密码

这篇 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 短信平台了 :)

Comments

要说加密,就一定先要保证有另一个“密”是通讯双方都知道而第三方所不知道的。离开了这个前提条件,就完全没有讨论意义了。

这另一个密,计算机出现前是加密方法,比如逐字替换之类;计算机出现之后,算法已经无需保密,只要保证密码不泄露就成了。

换言之,可以把我的客户端和服务器端源代码都给你,但你仍然还是只能想别的办法去盗取用户的密码——比如键盘记录、钓鱼网站或者暴力破解等等——却永远不要指望通过监听网络通信来得到它们。

事实上,现代达不到这个要求的系统,都可以认为是不设防的——这就是书本上反复告诫的“千万不要写自己的私有加密算法”的原因。


要达到这个目标,由于前辈的努力,其实也很简单。

1、初级阶段
不考虑第一个密码双方是怎么传递的,只保证只要这个密码不被窃取,双方通信内容就没有第三方可以看懂就行了——恩,希望不会有人出来扯什么可以站通信者旁边看。

这极其简单。比如,把日期时间+用户密码用des加密——可以约定加密密码同用户密码或者是用户密码的hash——然后,把加过密的包传给服务器。

由于之前已经假定服务器知道用户的密码,于是它自然就可以解开这个包,提取出用户的真实密码和登录时间——时间不对或解不开这个包,都应该拒绝登录。
对中间监听的第三方来说,在盗得用户密码前,除非暴破des,否则就无法知道双方通信的真实内容。

这很完美——但是,即使现在不考虑客户机中了木马,还是有一个问题无法绕过:这第一个双方都知道的密码该如何传递?

这正是云风在这里想讨论的问题(很显然,楼下各位根本就没理解云风这篇文章的意思),即:
2、通过什么方法约定第一个秘密?

最容易理解的就是RSA。就拿它举例吧。

首先,服务器公布一个公钥;客户端使用这个公钥通过RSA算法加密自己欲使用的密码(如12345),然后把加密结果传给服务器。

众所周知,使用RSA公钥加密的东西,即使是加密者自己也是不可能解开的,只有知道私钥的服务器才可能知道内容。

于是,客户端和服务器都知道了密码是12345。而第三方则只能在客户输入密码时盗取(或者暴破,或者严刑拷打^_^),除此没有其他办法知道密码究竟是什么。

然后,问题就回到1上,一切可谓完美解决了。


RSA很慢,且必须定期更换公钥;于是云风说到了Diffie Hellman。
这个算法我不熟悉。根据云风的描述,应该是这样:
客户端拿一杯水倒进箱子,传递给服务端;服务端往里面加入过量的钠,于是得到了氢气;现在箱子又被传回客户端,客户端继续往里面加水,于是也得到了氢气。
于是,双方就知道了密码是氢气。

而第三方则看得云里雾里,不知道这俩人在变什么魔术;他就是拿到了箱子,也不可能知道客户端和服务器端都往里加了什么——Diffie Hellman可不是化学反应那么简单。

但是,第三方可以这样玩:他接到客户端的箱子,不转发给服务器端,而是自己往里加钾,于是和客户端约定了钥匙是氢;然后,他自己构造个箱子,里面放氢,最终和服务器端约定密码是水。
然后,他就可以用氢来加解密和客户端的通信,又用水当密码和服务器端交互;然后,就只是一个简单的proxy实现的问题了——这就是典型的中间人攻击。

所以,这个算法无法完成安全约定第一个密码的任务,必须加以改进。


--------------------

很显然,一个设计合格的通信协议是无懈可击的。攻击者唯一能利用的就是想办法用其他途径获得密码。

对网游来说,就是限制盗号者们只能在客户端动手脚,靠键盘记录器之类东西作攻击了——而这点,才是wow的密保卡要解决的。

由于wow密保卡以及密保输入界面的设计,使得盗号者已无法通过键盘记录、截屏等手段获得密码。
事实上,目前情况下,wow盗号者唯一能采用的手段就是搞一个伪造的界面骗用户输入,然后根据这个输入在另一个地方完成登录——这有点类似中间人攻击;但区别是必须先攻破客户端机器并且必须替换或更改客户端程序(这可是一个很过分的要求!),并且很难如中间人攻击般不被发觉。

嗯,对有CA这个事情,我再想想其他办法。

BTW:我申请了TypeKey后,发留言就没正常过,每次都变Anonymous ,真奇怪

“仿真一个游戏服务器骗密码”也是做不到的。这个是由 CA 保证。

用同样的原理,只需要在 client 里内置服务器的 CA 或等价物就够了。至于 client 下载安全的保证,可以用标准的数字签名,现在 windows 都支持。

哦,是我原来理解错了。
当我看到文章标题的时候只是考虑如果防止密码被还原,看到你说用SSL防止被人窃听时,我就去考虑如果我要盗号应该如何做了,后来还去想逆向工程找算法、在其他机器放ARP病毒、仿真一个游戏服务器骗密码等等....呵呵,完全想歪了。我只会用SSL这些现成的方法,从未象你这样深入的去考虑。

我刚转网游开发这行还不到一个月,5.18日才从朋友口中知道你的BLOG,这几天看了你的不少文章,收益良多,非常感谢。以后可能有疑问,请多指教。

这不是要不要做文章的事情。
目前所谓反外挂反盗号技术都是些旁门左道。

但是信道加密是是理论基础的,可以做且一定要做的。它和前者是两样东西。它也不解决外挂和盗号问题。

比如,你在家上网,用的自己的笔记本或台式机,你绝对用方法保证你自己的机器没有木马。但是你保证不了电信的网关路由器会不会出问题。

信道加密的算法就是保证的这一点,你只用保证你自己的机器的安全。

对于现有的加密手段,比如 SSL ,是有能力避免即使

“有不怀好意的人从一开始就监听了你的所有通讯数据流并记录下来”

依然保证你的数据安全。

这和算法和加密协议本身是否被人了解无关。

本文也只是一篇粗浅的科普而已。

我明白你的意思。我这边的情况是我有一个开发网游的团队,他们也考虑过在算法上做文章,而我们同时也有一个用ida搞逆向工程的反编团队。两边一起去研究防盗号和防外挂的问题。

结果是反编团队研究后破解了魔兽世界、完美、巨人等游戏的加密和防外挂机制。

于是我只好和网游团队说,他们不需要想在算法上面做文章了,因为他们不一定能力找到比上述几个大公司更好的解决方案,但外面的外挂开发团队有能力破解这些机制。在时间上做文章,用服务器端做校验,在网络包被人完全监控,并且盗号者非常清楚我们的算法的情况下,只要盗号者无法还原密码就行了。

顺便说我们一下我们上面几个游戏研究防外挂的结果:
魔兽做得真好,他们做了一个虚拟机在客户端中防外挂,服务器端会传动态的代码过来要求校验客户端的执行结果,这个问题可以解决,但很麻烦。同时他们在服务器端做了非常多的校验,这两方面极大提高了外挂开发者的开发成本,难怪魔兽世界现在没有脱机外挂。

这篇 blog 讨论的问题是:

一旦 client 的通讯协议被破解,在保证 client 的机器不被动任何手脚的情况下,如何从算法上避免因为网络监听(不一定要在 client 上做,可以在通讯环节的任一点做)导致的密码失窃。

不好意思,刚才说的是另一个话题,没解释清楚再上一次说的话。

"既然各种加密都无法防止逆向工程,不如随便找一个!?" 打这一句时,当时是正在考虑你在文中提到的SSL,RSA等算法,觉得逆向后是可以看到这些算法的,也会有"如果有不怀好意的人从一开始就监听了你的所有通讯数据流并记录下来会怎样"这样的问题。实际上我们这边有人常干些破解的事情,我们常用的手段就是把代码逆向后做单步跟踪,把网络包拦截下来后回放,大多数情况下,脱壳后,直接把那段加解密算法的汇编代码嵌到自己程序后就可以用了,根本不需要去理会是什么算法。因此我觉得具体是什么加密算法对破解者来说,没太大区别。还不如不花什么心思,直接用MD5这种最多人会的东西,降低开发成本。

直接MD5当然和明文没有什么区别不行,人家做个钩子在客户端里面把网络拦截到的内容发给游戏服务器就可以登录。除了发用户名密码外,还需要发个时间戳,密码的密文是:密码加个字符串加上这个时间戳的字符串后MD5。这样每次登录的网络包都不一样,而服务器还可以通过校验这个时间戳超时来判断客户端是否真正被人逆向后单步跟踪中。这样的话就算被逆向了,人家知道了算法,而且把所有网络包都拦截了下来,也无法还原密码,也无法通过放钩子在客户端做网络数据回放。个人觉得很多情况下简单加个时间戳然后有服务器验证时间戳是否超时就可以解决很多安全方面的问题。

嗯,有人有心思通过逆向工程看到算法,然后通过穷举法来碰密码,那就没办法了。

@liangwj

你没明白这篇文章想说明的问题。传密码的 md5 是没有意义的,和传明文是等价的。

似乎无法避免client被人逆向工程,现在也没见到那个游戏的客户端是不能被逆向。我所知最复杂的是奇迹世界,可这个游戏甚至源代码都可以买得到。

既然各种加密都无法防止逆向工程,不如随便找一个!?例如找个MD5之类不可还原的算法把密码加密后传过去就算了

很好啊

我觉得千防万防,家贼难防。

不好意思,我不用 qq 。如果去 google groups 申请个 maillist 我倒很有兴趣 :D

云风能加这个Q群吗51064975
网游相关,主要是帐号安全方面的行业讨论群
需要你这样技术比较出色的人参与哇

汗 ,几个关键词打错字了。

就是因为RSA的计算速度不快,所以一般用来加密密钥,明文还是用DES来加密。若要再加强强度,可考虑3DES,但如果客户端被反向的话,通讯数据的加密算法再强也没有用,因为所有密文都会在客户端里还原成明文,这是近期头痛的工作问题之一。

另外,6楼同学提到的ECC不会是奇偶校验吧(汗)

就是因为RSA的计算速度不快,所以一般用来加密密钥,明文还是用DES来加密。若要再加强强度,可考虑3DES,但如果客户端被反向的话,通讯数据加密算法再强也没有用,因为所以密文都会在客户端里还原成密文,这是近期头痛的工作问题之一。

我们有程序也采用对键盘按键逐字符加密的

输密码时键盘逐字符加密

本文不涉及任何防木马的问题,这个问题我在 blog 上也讨论过许多次,有兴趣可以搜索。

解决木马问题是用户和开发商都需要努力;而本文提到的问题用户是无能为力,必须由开发商来做的。

还是没有解决木马盗号的问题,当你把密码输入密码框时,明文就已经在内存中了。

我们目前的项目做了类似 Kerberos 的系统,但是 Kerberos 类是基于密码挑战的,这需要对网易已有的 URS 认证系统做改造。基于非技术因素,暂时无法实施。

关于这一点,我过去两年已经写了好几篇了 :)

用密码挑战的方式来加强通讯安全无疑是最廉价的。

ECC 从性能和效率上讲,都比 RSA 好。

但是只在传送一小段密文上,对 RSA 并无明显优势。

RSA 的好处则是数学原理简单(高中生都能读懂),所以工程实现也简单。而 ECC 就过于复杂了。

Kerberos是更加合适的,
另外几十用非对称也不建议是RSA,应该是ECC

恩,学习乐,

不幸的告诉云风,我周围很多梦幻玩家都被盗过号。现在最大的敌人是木马,如果电脑是干净的,就极少被盗。毕竟对于盗号者来说,用那么大精力去破解是得不偿失的。

和谐社会个人是不能有隐私的。

关于RSA的比喻真是生动 :)

Post a comment

非这个主题相关的留言请到:留言本