From c32045059fb3f7098c84361eda2f03673b87a381 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 21:50:34 -0400 Subject: [PATCH] docs: add a password brute-force resistance report A defensive analysis of the password-wrapping layer: PBKDF2-SHA-256 at 310000 iterations, the ~99-bit generated password, and measured per-attempt cost (~48 ms/core here). A table estimates offline crack time by password type across phone, single-GPU and cluster attackers, showing generated passwords are effectively uncrackable while PINs and dictionary words fall in seconds to hours. Mitigations and the estimate script included; the security model gains a matching section. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01PCUwNSXQdqfDSugmFo4qmM --- README.md | 25 +++++++++++++++++++++++++ docs/html-room/security-model.md | 24 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/README.md b/README.md index 65e580f..ec7bd77 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,31 @@ curl -fsSL https://raw.githubusercontent.com/Freecode100Year/htmlroom/main/scrip 详见[安全模型](docs/html-room/security-model.md)和[协议说明](docs/html-room/protocol.md)。 +## 口令能扛住暴力破解吗? + +设了口令的房间文件,钥匙(32 字节主密钥)用口令派生的密钥包装起来。谁拿到文件,都可以在**离线、不限次数**地试口令——这是口令模式躲不开的威胁,所以口令的强度就是这道锁的全部强度。 + +**这道锁有多慢。** 口令不是直接当钥匙,而是先过一遍 [PBKDF2](https://en.wikipedia.org/wiki/PBKDF2)-SHA-256、**310000 轮**,故意拖慢每一次尝试。实测本机单核浏览器加密每次约 48 毫秒(约 21 次/秒);下表按更强的攻击者估算,`随机生成口令` 指页面「随机生成口令」给出的 20 位口令(31 个不易混淆的字符,约 99 位熵)。时间按平均情况(试遍一半空间)计: + +| 口令类型 | 熵 | 手机 / 笔记本 | 单张高端 GPU | 千卡集群 | +| --- | --- | --- | --- | --- | +| **随机生成口令**(20 位) | ~99 位 | 约 10²⁰ 年 | 约 10¹⁷ 年 | 约 10¹⁴ 年 | +| 8 位随机小写字母+数字 | ~41 位 | 约 450 年 | 约 330 天 | 约 8 小时 | +| 6 位随机小写字母 | ~28 位 | 约 18 天 | 约 51 分钟 | 约 3 秒 | +| 常见词 / 泄露库里的口令 | — | 约 2 个月 | 约 3 小时 | 约 10 秒 | +| 4 位数字 PIN(如 `1234`) | ~13 位 | 约 50 秒 | 瞬间 | 瞬间 | + +> 攻击者算力按保守估计:手机/笔记本约 100 次/秒,单张高端 GPU 约 5 万次/秒,千卡集群约 5000 万次/秒。真实数字随硬件和优化浮动,但**不同口令之间差了十几个数量级**这一点不会变。 + +**结论:** + +- **用「随机生成口令」**。它比任何集群的寿命都长,这道锁实际上破不开。 +- **自己想的口令**:越长越乱越好,整句比单词强得多;别用生日、`1234`、常见词或在别处用过的口令——这些在词典和泄露库里几秒钟就试到。 +- **口令换一条独立渠道发**(打电话、另一个聊天软件),别和文件一起发;310000 轮拖慢的是暴力破解,挡不住口令直接泄露。 +- 想完全不靠口令强度,就别用口令模式,改成**通过可信渠道把文件本身安全送达**,谁都拿不到文件就无从离线破解。 + +这些数字只针对口令这把「外层锁」。聊天内容本身由每对成员的 ECDH + ML-KEM-768 会话密钥保护,和口令是两回事。表格里的估算脚本和 PBKDF2 参数可在[安全模型](docs/html-room/security-model.md#口令抗暴力破解)查到。 + ## 常见问题 **微信 / QQ / iPhone 里点开文件没反应?** diff --git a/docs/html-room/security-model.md b/docs/html-room/security-model.md index 97f1a54..ebe04f8 100644 --- a/docs/html-room/security-model.md +++ b/docs/html-room/security-model.md @@ -47,3 +47,27 @@ V3 把入房、编号、在线名单和发送者标注从中继移到成员之 ## 定向投递的元数据 密钥帧的 `to` 让中继知道某一帧的密钥包装是给哪些成员号的。中继本来就分配成员号、知道谁在场和每帧的发送者,`to` 只多暴露「谁在给谁补发密钥」这一时序,不涉及任何密钥或内容。去中心化中继看不到成员号,`to` 在加密帧内部,只用于接收方跳过不属于自己的帧。 + +## 口令抗暴力破解 + +设了口令的房间文件把 32 字节主密钥用口令派生的 AES-256-GCM 密钥包装(`salt` 16 字节、`iv` 12 字节、`ciphertext` 48 字节,AAD 为 `private-talk/html-room/v1/wrap|`)。持有文件者可**离线、无限次**尝试口令,因此口令强度即为此层全部强度;310000 轮 PBKDF2-SHA-256 只拖慢每次尝试,不改变这一威胁模型。生成口令(`readablePassword`,`crypto.js`)为 5 组各 4 个字符,取自 31 字符表 `ABCDEFGHJKMNPQRSTUVWXYZ23456789`(去除 I、L、O、0、1),约 99 位熵,并对随机字节做去偏(丢弃 ≥248 的字节,248 = 8×31)。 + +单次尝试成本 = 一次 PBKDF2(310000 轮)+ 一次 AES-GCM 解密。本机(WebCrypto,单核 Xeon 2.1GHz)实测约 48 ms/次(约 21 次/秒)。原生优化代码与 GPU 会快若干数量级。 + +攻击时间估算脚本(平均情况取空间一半): + +```js +const rates = { phoneLaptop: 100, oneGPU: 5e4, cluster: 5e7 }; // 次/秒,保守估计 +const space = pw => ({ + generated: Math.pow(31, 20), // 随机生成口令,~99 位 + lower8digit: Math.pow(36, 8), // 8 位小写+数字,~41 位 + lower6: Math.pow(26, 6), // 6 位小写,~28 位 + dict: 1e9, // 词典/泄露库命中 + pin4: 1e4, // 4 位 PIN +}[pw]); +const seconds = (pw, rate) => (space(pw) / 2) / rate; +``` + +结果见 [README](../../README.md#口令能扛住暴力破解吗)。生成口令(~10¹⁴ 年起)实际不可破;弱口令(PIN、词典词、短随机串)在秒到小时级可破。缓解:使用生成口令或长随机口令句、经独立渠道传递口令、或放弃口令模式而通过可信渠道安全送达文件本身。 + +PBKDF2 轮数是文件配置项(`kdf.iterations`),读取时 `validate` 要求与内置常量(310000)完全一致,拒绝被调低轮数的文件,避免攻击者把现成文件改成更易破解的版本后再转给他人。此层仅保护口令外层;消息内容由每对成员的 ECDH + ML-KEM-768 会话密钥保护,与口令无关。