阅后即焚:对称密码的安全创建和分享
- IT
- 2022-12-02
- 858热度
- 1评论
问题的提出
众所周知,我们每天日常使用的通讯工具并非绝对“安全”和“私密”的。想要通过这些工具传递特殊文件,又不想泄漏隐私,很容易想到将文件加密压缩的方案。
首先考虑对称密码。对称密码使用同一个密码加密和解密,是日常生活中最常用的密码类型,也被所有流行加密软件支持(例如winzip、winrar)。但问题仍在:若A与B只能通过网络沟通,那么在不安全的网络中传递密码仍然有很大风险,对称密码一旦被第三方截获,文件加密也就失去了意义。
非对称密码被设计用来解决这个问题:你可以在本地创建公钥、私钥,只在网络上传递公钥。对方用公钥加密,你用私钥解密。然而,非对称密码使用并不广泛,需要专用工具,且对于普通人来说,理解和操作有一定的门槛。
如果找到一个方法,可以在不安全的网络上,安全地创建和分享对称密码,而又可以确保不被第三人得知,那就完美了。
Step1 “阅后即焚”初步方案
为了让密码不在网络上传输,设想一个第三方的临时地址。
A在临时地址产生并保存一个随机密码,将临时地址发给B,临时地址确保一旦有第二个人(B)访问,密码向B展示之后即刻删除。
如果第三方(C)比B抢先一步访问了临时地址,并窃取了密码,则B访问时将被提示“密码不存在”,从而得知密码已泄漏。此时A、B只需重来一遍即可,直至B顺利取得密码。
理论上讲,如果C完全掌握了临时地址服务器的最高权限,它有机会在“密码创建”-“分享地址”-“地址被打开同时密码被删除”这个窗口期从后台数据库得到密码。现实中,这个窗口期只有十几秒钟,这使得C实现该目标的难度极大。
好了,通过Step1,A与B(在C的眼皮子底下)分享了一个“只有你知我知”的密码,用该密码加密文件,就可以愉快地在网络上传递了。
Step2 多人共享同一个密码
独乐乐不如众乐乐。很快想到,如果多人在一个群组里,A如何才能将1个公用密码安全地分享给所有人(B1、B2……Bx)呢?
初步方案显然解决不了这个问题:为了分享给多人,密码做不到“阅后即焚”。由于不可能保证所有人都在极短的时间内访问临时地址,势必大大增加密码暴露窗口期,使得窥伺在旁的C可以浑水摸鱼。
所以这种情况下,必须对B1、B2……Bx进行身份认证,B必须提供一个令牌(Token),方能取得临时地址上的密码。
很自然地想到,初步方案中,A与B1、B2……Bx分享的密码P1、P2……Px,刚好可以作为Token使用。
A在创建公用密码(SP)的时候,预先提供若干个Token,当B访问时提供Token,临时地址校核Token确认无误后再提供密码,这就让C没办法冒充了。
考虑到此模式下,无论是公用密码(SP),还是作为Token的P1、P2……Px,都需要在临时地址存储较长一段时间(数秒至数小时,人多的时候不可避免),显然不能在临时地址中存储密码明文。
那么可以将Token用不可逆算法md5编码后存入数据库,用于比对B的身份;同时,将SP与每一个原始Token进行混合,在数据库中仅存储混合后的Hash-SP。
具体逻辑如下:
1、A首先与B1、B2……Bx通过Step1创建P1、P2……Px。
2、A访问临时地址,依次填入P1、P2……Px,提交后,系统生成随机公用密码(SP),将每一个P1、P2……Px与SP进行可逆的混合操作生成并保存Hash-SP1、Hash-SP2……Hash-SPx。相应保存Md5(P1)、Md5(P2)……Md5(Px)。
3、此时C如果得到了数据库,由于Md5不可逆,它无法得到P1、P2……Px,也就无法破译Hash-SP1、Hash-SP2……Hash-SPx,从而保证了公用密码SP的安全。
4、货真价实的B,由于手握Token(P1、P2……Px),则可以顺利通过Md5身份验证,并通过Token本身对Hash-SP进行逆操作,得到最终的公用密码SP。
5、每个Token取得公用密码SP后,即刻从临时地址服务器中删除。
C急得跳脚的核心关键在于,在Step1、Step2中,A与B(们)始终没有通过网络传递密码明文,也没有提供任何可以窥探的机会。
Step3 传递已知密码
在Step1、Step2中,为了保障密码的质量,可以把生成复杂随机密码的重任交给临时地址服务器来执行。
然而,假设此时有1位值得信赖的朋友X新加入了群组,怎样把已知的公用密码SP发送给X呢?全部成员当然可以按Step2再来一次,但这样做显然比较麻烦。
为了解决这个问题,可以再打一个功能补丁。形式上类似Step2,但这一次公用密码SP不再由系统随机生成,而是由A指定。A首先与X按照Step1创建密码Px,随后,将指定的SP与Px一起填入临时地址。临时地址服务器采用与Step2相同的方式对SP、Px加密存储后,新成员X就可以利用Px作为Token取得SP了。
Step4 版本升级记录(20260609)
旧版本运行多年之后,回头再看,仍然有一个不够完美的地方。
在Step1中,单人分享密码虽然做到了“阅后即焚”,但随机密码毕竟曾经以明文形式保存在临时地址服务器上。虽然窗口期很短,通常只有数秒到数十秒,但如果C已经攻陷服务器,并且能够实时监控数据库,那么理论上仍有机会抢在密码销毁前得到明文。
在Step2、Step3中,情况稍好一些。公用密码SP不再明文保存,而是通过Token混合后存储。但加密、解密动作仍然发生在服务器端。换句话说,服务器虽然没有长期保存明文,但它仍然“有能力”接触明文。
那么,能不能让服务器彻底失去这种能力呢?
这就是新版升级的核心思路:所有敏感操作都放到浏览器本地完成,服务器只保存无用密文。
具体逻辑如下:
1、A打开临时地址,浏览器在本地生成随机密码和加密密钥。
2、浏览器立即用密钥加密密码明文,得到密文。
3、浏览器只把密文上传到服务器。服务器保存密文、有效期、领取次数等信息,但从始至终不知道明文,也不知道完整密钥。
4、系统生成分享链接。链接中,服务器需要知道的编号放在正常地址里;真正用于解密的密钥放在#后面。
例如:
https://example.com/key/default.php?id=xxxxx#c=yyyyy&k=zzzzz
这里有一个关键点:浏览器访问网页时,#后面的内容不会发送给服务器(HTML规范)。也就是说,服务器能看到id=xxxxx,却看不到k=zzzzz。没有这个密钥,服务器数据库里的密文对C来说也只是一堆无意义字符。
5、B打开链接后,浏览器从服务器取回密文,再从地址栏#后面取出密钥,在本地完成解密。解密完成后,服务器立即销毁对应记录。
多人分享密码和传递已知密码也沿用了同样的思想。
在多人模式下,A仍然可以预先为B1、B2……Bx准备Token。但新版不再让服务器用Token解密密码,而是由浏览器在本地使用Token参与加密。每个Token都会生成一条独立的密文记录。B1使用P1取得自己的那条记录,B2使用P2取得自己的那条记录。每条记录一旦被成功领取,立即销毁。所有人都领取完后,服务器上也就不再留下任何对应密码。
在传递已知密码模式下,SP不再由系统随机生成,而是由A输入。但SP同样不会以明文提交给服务器。浏览器会先用Token在本地加密SP,然后只把密文交给服务器保存。X取得链接后,必须输入正确Token,才能在本地解密得到SP。
这样一来,C即使拿到了服务器数据库,也只能看到密文、过期时间、领取状态等信息。没有链接中#后面的密钥,或者没有正确Token,就无法还原密码。
当然,事情还没有神奇到可以对抗一切情况。如果C已经完全控制服务器,并且能够篡改网页代码,那么它仍然可以给后续访问者下发恶意脚本,试图在浏览器端偷取明文或密钥。这是网页工具无法彻底避免的问题。后续还需要增加一个独立的 JS 校验服务。
但新版至少把风险从“看到数据库就可能得手”,提升到了“必须控制并篡改服务器代码才有机会得手”。对于这个工具最初想解决的日常场景来说,安全边界已经前进了一大步。
此外,新版也顺手完成了技术栈升级:原先的ASP + Access版本,重构为PHP + SQLite版本。数据库更轻量,部署更简单,也更容易维护。
最后需要说明的是,有效期删除采用的是“懒清理”机制。PHP本身没有后台常驻任务,因此服务器不会在到期那一秒主动删除记录。但每次有人访问页面、创建密码、领取密码或销毁记录时,系统都会顺手清理过期数据。即使某条记录已经过期但还没被物理删除,领取时也会先检查有效期;一旦过期,服务器不会返回密文,并会立即删除该记录。
至此,工具从“服务器临时保管密码”,升级为“服务器临时保管密文”。服务器不再是密码的知情者,而只是一个临时中转站。
基于上述逻辑开发的网页小工具Password Exchange Tool网址如下:

[…] 我们经常为发文件发愁:要么文件太大,微信、QQ邮箱超大附件经常放不下;要么文件特别私密,用第三方IM软件发送总是不放心。我之前的blog曾经提过两种解决方案:一是阅后即焚的对称密码传递方案,但是操作起来稍显繁琐;二是用RSA非对称加密,使用者理解起来有一定的门槛。而且两者都只能解决隐私问题,不能解决大文件问题。 […]