Repository navigation
一次深度安全评审后的版本,核心是密钥分离。密文格式换代到 0x03/0x04,
但双读:1.2.x 写出的旧密文无需任何操作即可照读。
安全
- 密钥分离(HKDF-SHA256)。 DB 路径的确定性 ECB 对任意明文块给出
E_K(块),本质上是一台加密预言机 —— 而此前的 README 与测试都教用户两条
路径共用同一把主密钥。那会让能往 DB 侧列写入可控值的攻击者借它取得
GCM 的 keystream(命中行约前 2 KB 明文)与 GHASH 子密钥 H(16 个 NUL 即可,
无条件),机密性与完整性同时失守。现在主密钥只做派生:应用侧与 DB 侧各拿
一把互不可推的子密钥(info分别为encryptable-rust/aead/v1与
encryptable-rust/db-ecb/v1)。「两条路径配同一把主密钥」从危险变为
推荐。金标量由 Pythoncryptography独立复算、openssl 交叉验证。 - 上下文绑定。 新增
seal_with_context/decrypt_with_context/
decrypt_text_with_context/rotate_to_current_key_with_context(Guard
同名转发):上下文进 AAD,把密文钉死在「哪一行/哪一列」上,防「把别人的
密文粘到自己行上」。此前 AAD 只有版本字节,跨行跨列的密文搬移照样过认证。 - DB 解密错误文案折叠。 补位长度非法、补位字节不自洽、结果不是合法 UTF-8
曾各有可区分的消息 —— 在攻击者能触发解密的场景里就是一个 padding oracle
(逐字节改密文看错误分类,可还原D_K(·))。现在折叠成一条,与「密钥错」
也不可区分,与应用侧 tag 校验失败的做法一致。
修复
DbEncrypter::encrypt不再永久误拒AQ/AR/AS/AT开头的合法明文。
首字节恒为0x01由前两个 base64 字符唯一决定,因此 24 字符及以上的此类
业务码(订单号、券码、SKU)此前每次被拒,且 DB 侧没有绕过口。现在形状
闸带长度前置(真应用侧载荷 base64 后恒 ≥ 40 字符),并新增
DbEncrypter::seal()作为显式绕过口。decrypt_or_original不再返回类型名。Value::Int(42)曾经解出"int"——
既不是原值也不是原输入,是静默的数据损坏。现在按Display渲染("42",
Null→ 空串)。- AES-128 的 hex 密钥可以解析了。 hex 分支此前写死 64 字符:AES-128 的
to_hex()输出(32 字符)喂回解析器会被当成 32 字节字面量拒绝。现在按
「密码要求长度的两倍」判定。 EnvConfig::from_env()的 panic 文案修正并补## Panics文档 ——
此前在ENCRYPTION_PREVIOUS_KEYS写成 JSON 时 panic,消息却写着
「未设置时不可能失败」,与事实相反。DbEncrypter::new在配置带previous_keys时向 stderr 打警告:DB 路径
没有密钥环,照应用侧剧本轮换会让 SQL 一侧静默变 NULL(MySQL)或报错
(pgcrypto)。此前只有 README 里一句「已知上限」。- CLI:移除空操作的
--raw(帮助文本曾列出它);管道输入 trim 全部尾随
空白(修 Windows 风格\r残留导致解密静默失败),不再Box::leak;
--driver严格校验且只对sql命令有效;无参数时用法说明改走 stderr
(stdout 留给正常输出);keygen的 hex 拼装单次分配。 - warp 的 pet 过滤器补
path::end():/pet.svg/anything不再被吞,
与其余七个适配器的精确路径一致。
变更(行为收紧)
- 新密文一律
0x03(应用侧)/0x04(DB 侧);0x01/0x02旧格式只读,
rotate_to_current_key()会把旧格式一并搬成0x03。is_encrypted两代都认。 decrypt_expr产出按格式字节分派的 CASE 表达式,两代密钥都内嵌 —— 迁移期
的混合列一条语句全读;旧数据搬完后可改用只含子密钥(derived_key_hex())
的手写语句,把主密钥从 SQL 里拿掉。pet::data_uri()返回&'static str(LazyLock静态化,README 示例中它是
每请求路径,此前每次重编码 4.7 KB);新增pet::DATA_URI常量。Value的Debug遮蔽字符串载荷(decrypt()的返回值最容易被
tracing::debug!(?v)随手记录,而它是明文 PII);ArrayConfig/EnvConfig
的密钥字段Zeroizing化、Debug手写遮蔽 —— 此前dbg!(config)会直接
打印主密钥。Error与Cipher标注#[non_exhaustive]:下游穷尽match需要补
_分支。趁 1.x 早期收紧,之后再补代价只增不减。
性能
- 形状判定的长度前置(短于真载荷最短 base64 字符数的输入免解码再判):
DB 写路径实测 ~-33%、应用侧 ~-4%;顺带缓解「为看一个字节全量解码」的
大输入放大。判据是必要条件,不会漏判任何真密文 —— 语义不变。 pet::data_uri()编码只做一次。
测试
- 新增
tests/cli.rs:子进程级契约(keygen→encrypt→decrypt 往返、退出码
0/1/2、管道输入与尾随空白、--driver严格性、SQL 命令的 stdout/stderr 分工)。 - 新增:上下文绑定(含错误上下文、跨路径拒绝、轮换保持绑定);旧格式
0x01
载荷的手工构造与双读;「当前格式用子密钥而非主密钥」的源码级断言(两侧);
并发 nonce 唯一性(8 线程 × 200);1 MiB 往返;DB 忽略previous_keys钉死;
AES-128 金标量;AQ前缀误拒回归。 - 金标量换代:
0x04向量由 Pythoncryptography+ openssl 独立复算;
原0x02OpenSSL 向量转为双读回归锚(只断言解密,不再断言加密输出)。
文档
- README(中英):载荷格式表、新增「密钥分离」一节、SQL 片段为 CASE 形态、
上下文绑定用法、CLI 提示、已知上限逐条同步;修掉框架集成安装示例的
version = "0.1"(crates.io 从无此版本,直接复制会解析失败)。 - 全仓库文档与注释复核:设计理由全部改写为自洽的叙述,不再引用任何外部
实现;README 删去引用外部实现来源的一节(中英同步)。
CI / 发布
- clippy 与 doc 改
--all-features:八个集成模块此前从未被 lint / 断链检查 ——
1.2.1 修的那批框架文档示例错误正是被 feature 门挡在 CI 外的类型。 - 全部 cargo 命令加
--locked(lock 已提交:有 bin 且 MSRV job 靠它钉版本);
package job 去掉--allow-dirty。MSRV job 加--all-features。 actions/checkoutv4 → v7;新增dependabot.yml(cargo + actions,每周)
与每周的cargo audit工作流(加密库的 RUSTSEC 公告不该靠人记得)。Cargo.toml补[package.metadata.docs.rs] all-features = true—— 此前
docs.rs 上八个集成模块与 serde API 完全不可见。
依赖
- 新增
hkdf0.12 与sha20.10(用途子密钥派生 —— 安全原语不手写)。