Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 里点开文件没反应?**
Expand Down
24 changes: 24 additions & 0 deletions docs/html-room/security-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -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|<roomId>`)。持有文件者可**离线、无限次**尝试口令,因此口令强度即为此层全部强度;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 会话密钥保护,与口令无关。
Loading