返回
1335 字
7 分钟
0 次阅读
密码就是钥匙:我把加密改了三次

这部分是我整个项目里改得最多的地方,前后推倒重来三次。 记下来是因为第三次那个想法,是我自己想通的,感觉挺爽。

第一次:把密码存起来比对#

最开始我写的是最土的那种:聊天密码存在服务器的配置文件里, 家里人输一个,跟文件里的比一下,一样就放进去。

这样能跑,但问题很明显:打开那个配置文件就能看到密码。 而且聊天记录是明文存在硬盘上的 —— 谁把电脑拿走、或者随便翻一下 data/ 那个文件夹,里面说了什么全都看得见。

我当时的想法是”反正就家里人用,谁会去看”。但后来想想不对, 这个项目的卖点就是”不上云”,结果东西在自己电脑上全是明文,那不上云有什么意义。

第二次:想上 HTTPS#

我想的第二个办法是套 HTTPS,就是浏览器地址栏那个小锁。 这样路上跑的东西就是密的了。

试了才发现不行。家里没有域名、没有证书,只能自己签一个。 自己签的证书浏览器不认,会跳一个大红页,写着”此连接不安全”, 要点”高级”→“继续前往”才能进去。

我自己知道怎么回事,点两下就过去了。但我妈不行, 她看到红色的东西第一反应就是关掉。这个方案当天就被我放弃了。

这段经历现在写在 README 的”技术上的几件事”里, 原话是”自签证书浏览器一片红,还得教家里人点’继续访问’,不划算”。

第三次:让密码本身当钥匙#

最后一次是我想通的:与其”存一个密码、拿输入的去比对”, 不如根本不存在密码这个东西,密码直接就是解密用的钥匙。

流程是这样的:

密码
│ PBKDF2-HMAC-SHA256,跑 30 万轮 + 一段随机盐
▼
钥匙(只存在内存里,服务器一停就没了)
│
├── 用钥匙加密每条消息(HMAC-SHA256 做的流密码)
└── 先加密,再对密文签名,防止路上被改

好处是服务器上根本没有”密码”这个东西可以偷。 配置里只存了一段盐和一份用来校验的东西, 拿走去别的机器上跑,也推不出原来的密码 —— 因为要跑 30 万轮,这个轮数就是故意设这么大,让人硬猜的代价变高。

改密码为什么要把所有记录重新加密一遍#

这是这套方案最麻烦的地方,我一开始完全没意识到。

别的软件改密码,就是改一下数据库里那一行。 但我这个不行 —— 密码就是钥匙。钥匙换了, 以前用旧钥匙锁上的东西,新钥匙打不开。

所以 改密码.command 双击之后干的事是: 把所有聊天记录用旧钥匙解开,再用新钥匙重新封一遍,一条都不落。 改之前会自动备份一份到 data.备份-时间戳, 反悔了把那个文件夹改回名字就行。

顺便说一个我现在觉得挺得意的小设计: 密码存的地方是 data/config.local.json, 不在代码里。所以整个项目传到 GitHub 上也不会带出我家的密码。 那个文件权限是 600,只有我自己读得到。

我怎么知道加密没写错#

这是踩了坑之后才补上的。

写加密最容易犯的错不是”想不出算法”,是以为自己写对了。 我第一次写完自己测了一下,加密再解密,出来一样,就觉得没问题。 后来改别的地方动到了这段代码,改完再测,还是”一样”, 但我其实已经破坏了签名的顺序 —— 它还是能解出来, 只是”防篡改”这件事悄悄失效了。

所以现在有 selftest.js:用标准测试向量跑一遍, 输入是固定的,输出必须一位都不差。还有 crosstest.js, 专门验证手机端和服务端两边的加密能不能对上 —— 两边我是分别写的,很容易一边改了另一边忘了改。

Terminal window
cd server
node src/selftest.js 加密层的标准测试向量
node src/crosstest.js 手机端和服务端的加密对不对得上

老实说清楚它防不住什么#

这个我觉得比吹自己加密写得好更重要。

局域网里跑的还是明文 HTTP,内容本身是密的, 但抓包的人能看出来”谁在跟谁说话、什么时候、大概多大的量”。 这个我防不了,除非上 HTTPS,而上 HTTPS 那条路前面已经说了走不通。

还有浏览器里拦不了截屏 —— 网页没有这个接口,只有原生 App 能拦。 别人拿另一台手机拍你的屏幕,这个任何软件都防不住。 这两条我都写在 README 的”已知限制”里了,没藏着。


加密那部分代码在 server/src/crypto.js 和 app/js/crypto, 两边是一套东西分别实现的。

这篇文章讲的东西,可以在这里下载

全部东西都在下载区, 包括模组、地图和软件。

密码就是钥匙:我把加密改了三次
https://liyuanjie.com/posts/third-post/
作者
Liyuanjie
发布于
2026-05-03
许可协议
CC BY-NC-SA 4.0
这篇被看了 0 次