程序员入伙书——“会报警”的取款机
有个传言流传很久了,说是如果你被歹徒胁迫去ATM取钱,不妨把密码倒着输,如123456输成654321,然后,ATM就会一边吐钱给你(为了防止歹徒翻脸攮你一刀),一边偷偷替你报警。
这个传言的虚假性挺明显的,不过就我所知,办公室里曾经有人,看到这消息时,兴奋地念给大家听,完全辱没了自己程序员的身份。最近一念忽然想起了搁笔已久的《程序员入伙书》,该继续往下写了。这一篇并不算是真正介绍编程语言或技巧的,而是作为《程序员入伙书——两个世界》的补充篇,帮助大家习惯计算机的思路。
即使不是学编程的读者,这篇文章也可以算是足够浅显的逻辑训练,教大家识别都市传说真伪的套路。
假如我是程序员,被客户——或者啥也不懂、就知道和客户沆瀣一气的销售员——要求设计这样一个ATM:正着输密码就心平气和地吐钱,倒着输密码就边吐钱边报警。
我的第一个问题就是:万一用户的密码是888888怎么办?(暴发户脑子不好使,就记得这种密码)或者稍微复杂些的:158851,你到银行输这个密码,取款机该不该报警?当场就把在办公室传这个消息的人们问趴下了,我想,给我布置这个任务的客户也只好收回成命。
接着再说点深的,我努力做到循序渐进,先易后难。
首先说的就是,这个传言有个过于简单的假设(请记住“过于简单”这个词,文章结尾要用),就是“银行知道客户密码,所以知道密码是反着输的”。山寨银行或者骗子银行可能真知道客户密码,但正规的银行一定不知道,理由有浅有深。
浅显的原因是,银行的老大也要存钱,银行老大一定不喜欢任何人——柜台、出纳、甚至机器——看到或记住他的密码。你可能会问为啥让机器记也不可以,因为机器是人类维护的。如果银行知道客户密码,知道底细的老大就不愿意在自家银行存钱,更不愿意把钱存到别家银行。平民百姓也倒罢了,那几个小钱有跟没有差不多,如果银行的任何角落里留有客户密码,那么越有钱的人越不敢把钱往银行里搁。
深一点的原因,是这样可以保护银行自己。虽然银行都很贪,也确实在拿着所有客户的银子胡来,他们却不敢偷具体客户的存款,非但不敢偷,甚至还要做到“想偷都偷不出”。这样,万一有人状告银行在系统地盗用客户密码,银行就可以向调查者公开源程序:你们看,我们的系统真的无法窃取或透露用户密码,所以任何用户的钱因为取款取丢了,都是他自己没看好密码,我们没有赔偿责任。
定理:胆敢存储用户密码的银行,一定无法开张、即使开张也倒闭得最快最惨。
所以,每当我看到老太太们费力地按了六个键之后问柜台:“我按的是441205么?”再补一句“就是我的生日。”我都禁不住打哆嗦:老太太你这是要害死小姑娘啊!人家真的没看见你的密码,也不想听见你说的。再说你自己耳朵背,就非得嚷这么大声啊?我听见你的密码,都起歹意了……
那观众就要问了:银行不知道我们的密码,那我们怎么取钱的?取款机怎么知道我们输对还是输错了?
答案是这样的:银行虽不存放客户密码的六位数字原文,却存放着这六位数字被加工之后的结果。你设置密码时,机器把你按的六位数加工一下,改成一串面目全非的字母数字,谁看了也猜不出原文是啥,然后在系统里保存这个加工结果。你跑到取款机上取钱时,输入六位密码,取款机按照完全相同的步骤,把你输入的数字加工一遍,也得到了一串谁也猜不出原文的字母数字,然后把这串字母数字和银行系统存的那串东东逐字比较,如果完全一样,就吐钱打发你走,否则就拒绝服务甚至吞卡。
这个对客户密码加工的过程叫做“加密”,加密出来的产品叫做“密码”,为了不至于和我们习惯说的“银行密码”混淆,下面再提到银行取款用的密码时,我用的词将是“口令”或者“明码”。“明码”者,是相对“密码”而言,一串能被我们轻易记住的数字,实在是太赤裸裸、太暴露了。
用这套新的术语重述刚才的故事,就是:银行一定不知道用户的口令,却保留着由用户口令加密后的密码。用户设置口令时,银行把口令加密,并保留密码。用户使用取款机时,输入口令,取款机把口令加密,把密码传给银行,银行调出用户的密码,和取款机传来的密码对比,决定是给钱还是吞卡。
定理:只有输入口令的一刹那,银行或取款机的某个处理器知道明文口令是什么,加密一旦完成,银行的任何地方都不再有你输入的那串数字的痕迹。你按的数字不显示在柜台人员的屏幕上,她们说“不知道你的口令”不是骗你,不但她们不知道,整个银行都不知道。
帐户保管者不知道你的口令,还能允许你取钱,在计算机世界里是常见的事情,而人类世界里,或许只有一些密码锁保险箱能够媲美。
如果你看懂了,我就再说得深一点。
银行的加密过程是不可逆的,就是说,不但人眼读到密码后猜不出它的原文是什么,连机器也算不出来,甚至,即使你完全知道加密过程,也没法倒推回原文。“不可逆”本身不难理解,比如我给你一个数37,你无法判断它是用35+2算的、还是21554091÷582543获得的。
当然,37这个例子只能说明“不可逆”是合理的,却不能轻易说服大家,格式单一的六位数如何能够被加工得猜不出来。所以我接着说:你可以把加密理解为炒菜,但不像“回锅肉是肉做的、酸菜鱼是酸菜和鱼做的”那么明显,不可逆加密类似于跨物种的转换:把白菜放进锅里,开盖时蹦出来斑马。把椰子放进去,跳出来一头大象——你猜不出菜刚刚下锅时的样子。还有,这个烹调过程对细节十分敏感:白菜叶子多一片少一片,下锅时头朝上头朝下,出锅时就不是斑马,而是鬣狗鳄鱼或者别的怪物。
举个例子大家感受一下:“ABiELdbxGY2fY”和“AB6JwoYF632kM”,第一个是123456的密码,一个是123457的密码。至于654321(倒着输入的123456),密码是“AB39psZKlg/3M”。单看这三串怪东东,真的不相信它们原本就只差一点点啊。
不要被这句“真不相信它们原本只差一点点”误导,以为我强调的是加密结果太怪异难懂,存着“只要努力地算,还是能解密的”幻想。我实际说的是:银行用的加密算法是不可解密的,而非难以解密的。不可逆加密算法是许多计算机科学家(主要是数学家)智慧的结晶,是从数学上被验证不可逆转的。同时,“不可逆转”也让银行更有底气面对有关帐户安全的法律诉讼。
但是细心的观众可能还是留意到了,这三个密码有一个共同之处,就是头两个字都是“AB”,猜测它暗含着这三个口令的共同特征,可能成为倒推回去的钥匙。所以我再说深一点:除了对原材料敏感外,加密算法还引进了一个狡猾的概念:佐料。对于同样的一棵白菜,加密算法可以做醋溜也可以做麻辣,可以放芥末也可以放咖啡,出锅时可能是斑马袋鼠,也可能是兀鹫海豚。加密结果必须含有这个佐料信息,否则取款机猜不出当初是用椒盐还是黄油加密的,就真的没法比对密码了。那三串密码的头两位都是AB,因为我给它们放了相同的佐料:“AB”。整个十三位密码里,为了让取款机能够重复最初的菜谱,只有代表佐料的头两位不加密。
为什么要用佐料?因为要迷惑那些没事就琢磨密码的人。即使我们的一百套安全系统的口令都是123456,我们也要让它们的加密结果看起来不一样,让攻击系统的人(以系统维护人员的概率最高)不敢轻易拿偶然瞥见的一处口令去尝试系统的其它部位。
用实例感受一下吧,所有下面这些加密串,代表的口令都是一样的:“123456”。它们看起来如此不同,是因为添加了随机产生的佐料,每串密码的头两个字符就是。
VkEypoHtWIksI Tu6B/8kqiQYdg VaB3rD8VDSfdE BesKp0kUld3S6 Jo4Lj42LyLIgY Lqz9cEeSCd9K6 Rg7FlH70Zz/Uc Jq5So2MtUKiGo BwPN9PnRNkkvM TeNSjZVGF6O3w
以上内容的文字太繁杂,下面用个实战事例总结一下。
设置密码
- 我输入“123456”。
- 银行的计算机随机挑选了一款佐料,例如“Vk”,把“123456”加密为“VkEypoHtWIksI”。
- 因为担心我按第一次密码的时候手抖出错,自己都不知道,银行请我再输入一遍口令,以便确认。
- 这次我却真的手一抖,把“1”按了两次,结果成了“112345”。
- 银行的计算机继续使用佐料“Vk”,烹调之后发现结果是“Vk.cbF419Liz6”,两次密码不一样。但它不知道哪一次是正确的,所以要求我重头再按两次口令。
- 这次我做对了,两次输入的都是“123456”,银行使用相同的佐料,两次得到相同的密码,满意了。
- 银行保存密码“VkEypoHtWIksI”,并立即彻底忘记明文口令“123456”。
取款机
- 我插卡。
- 取款机问银行,这个帐户的密码是?
- 银行告诉取款机:是“VkEypoHtWIksI”。
- 我输入“123456”,不小心手一抖,按到了“6”上方的“9”。
- 取款机用“123459”和“Vk”一起下锅,算出了“Vkw7/ji6z0FCA”。
- 取款机对比了“VkEypoHtWIksI”和“Vkw7/ji6z0FCA”,表示很不满,嘟嘟地哼了两声,警告我说:重按!再乱来就吞卡了!
- 我又按了一遍,这次我按对了“123456”。
- 取款机算出了“VkEypoHtWIksI”,说:好吧这次总算对了。亲,你想要多少银子?
为了表述方便,这里写的过程可能过于简单,而银行未必是这么做的。另外,加密过程可能由取款机独立运算,也可能由银行中心系统来算,全看设计者对于“哪个信息被截获的后果最严重”而定。
再回到这个话题的引子。我说过,“银行知道明码口令”是“会报警的取款机”的“过于简单的假设”,那么如果我们假设得复杂一些,“会报警的取款机”究竟能不能实现呢?其实可以:开户时,只要在取款口令之外再添一项报警口令(必须和取款口令的加密结果不同);或者,在不允许对称口令的前提下,可以把取款口令反转后加密,存为报警密码。如果你被胁迫取款,输入报警口令,取款机完成加密后,和取款密码和报警密码分别比对,就可以决定要不要悄悄报警。
刁蛮的客户或销售以为看到一丝光明了,就接着逼我重接这个项目,所以我必须跳出计算机的世界,告诉他们一些人类世界里的真相:
其实啊,钱在现场是一定得给歹徒的,所以你用取款机报警和过后五分钟单独报警的区别不是太大,除非歹徒拿到钱后还坚持要捅你几刀。还有就是,如果真有那么一天,全国的银行决定添加报警口令功能,这几刀就更加不能幸免了。
由此可知,在办公室里兴奋地把这个传言念出来的,不但辱没了自己的程序员身份,作为人类也是相当掉队了。