symbolicsoft/hpke-ng

GitHub: symbolicsoft/hpke-ng

hpke-ng 是一个用 Rust 全新实现的 HPKE(RFC 9180)库,通过类型驱动的密码套件选择将模式不匹配、密钥错配等常见错误从运行时错误提升为编译期错误,同时在性能和内存占用上显著优于现有实现。

Stars: 18 | Forks: 1

# hpke-ng [![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/b14245f9d0034508.svg)](https://github.com/symbolicsoft/hpke-ng/actions) [![License](https://img.shields.io/badge/license-Apache--2.0%20OR%20MIT-blue)](#license) 一个全新编写的 [HPKE (RFC 9180)](https://www.rfc-editor.org/rfc/rfc9180.html) Rust 实现,采用类型驱动的密码套件选择。 ``` use hpke_ng::*; use rand_core::OsRng; type Suite = Hpke; let mut os = OsRng; let mut rng = os.unwrap_mut(); let (sk_r, pk_r) = DhKemX25519HkdfSha256::generate(&mut rng)?; let (enc, ct) = Suite::seal_base(&mut rng, &pk_r, b"info", b"aad", b"hello")?; let pt = Suite::open_base(&enc, &sk_r, b"info", b"aad", &ct)?; assert_eq!(pt, b"hello"); # Ok::<_, hpke_ng::HpkeError>(()) ``` ## 为什么需要新的 HPKE crate? `hpke-ng` 的出现是因为现有 Rust HPKE 生态中的三个摩擦点不断引发真实的错误和开销: 1. **Provider 抽象开销。** 基于 trait 的可插拔后端将分发成本推入热路径,并将 `Hpke` 结构体膨胀到数百字节——而对于类型系统已经知晓的值来说,这并不必要。 2. **结构体拥有的 PRNG 风险。** 当 `Hpke` 实例拥有其 RNG 时,克隆会隐蔽地使随机状态发生别名共享。结构上的修复方法是:不要拥有它。 3. **类型系统缺口。** 针对特定模式参数的 `Option<&[u8]>` 将缺少 PSK 和模式错误变成了本应是编译期错误的运行时错误。 该设计对每个问题采取了一个立场:**没有 Provider 抽象,没有拥有的 RNG,使用类型参数代替模式枚举。** 数学计算是一个已解决的问题;周围的库才是工程上仍有改进空间的地方。 ## 设计亮点 - **类型参数化的 API。** `Hpke` 是零大小的;密码套件存在于类型系统中。不匹配的原语会直接触发编译错误。 - **每种模式四个显式方法。** `seal_base`、`seal_psk`、`seal_auth`、`seal_auth_psk`——对于模式必需的参数,没有使用 `Option<&[u8]>`。 - **在类型级别将 Auth 限制为 DHKEM。** `Hpke::::seal_auth(...)` 无法编译。 - **在类型级别将仅导出(Export-only)进行限制。** `Hpke::<_, _, ExportOnly>::seal_base(...)` 无法编译;只有 `*_export*` 方法可用。 - **类型标记的密钥。** 私钥在其类型中携带了其 KEM,因此将 `DhKemP256` 密钥传递给 X25519 套件会被编译器拒绝,而不是在运行时才发现。 - **调用者提供的 RNG。** 配置不拥有任何 PRNG;克隆不会导致随机状态别名。 - **结构性的 Nonce 重用预防。** `Context` 是不可克隆的,并且当 `seq == u64::MAX` 时拒绝加密。 - **默认使用 `no_std` + `alloc`。** 启用 `std` feature 可为 `HpkeError` 提供 `std::error::Error` 实现。 - **单一的 Provider 栈。** 所有原语均来自 RustCrypto-org 的 crate。 ## 编译期保证 | 操作 | 其他实现 | hpke-ng | |------------------------------------------|-----------------|--------------------------------| | 在非 DH KEM 上调用 `seal_auth` | 运行时错误 | 编译时错误 | | 使用了 KEM 不匹配的私钥 | 运行时不匹配| 编译时错误 (类型标记) | | 提供了 PSK 的 Base 模式调用 | 运行时错误 | 编译时错误 (无 PSK 参数) | | 使用 `ExportOnly` AEAD 进行加密 | 运行时错误 | 编译时错误 | ## 支持的密码套件 | 组件 | 变体 | |---|---| | KEMs | `DhKemX25519HkdfSha256`, `DhKemX448HkdfSha512`, `DhKemP256HkdfSha256`, `DhKemP384HkdfSha384`, `DhKemP521HkdfSha512`, `DhKemK256HkdfSha256` | | KEMs (后量子,`pq` feature) | `XWingDraft06`, `MlKem768`, `MlKem1024` | | KDFs | `HkdfSha256`, `HkdfSha384`, `HkdfSha512` | | AEADs | `Aes128Gcm`, `Aes256Gcm`, `ChaCha20Poly1305`, `ExportOnly` | | 模式 | Base, Psk, Auth, AuthPsk | ## 性能 在与 `hpke-rs` 进行的 62 项正面基准测试中:`hpke-ng` **赢得了 43 项**,在底层原语占主导地位的情况下有 **14 项平局**,在个别密钥生成路径上 **输了 5 项**。最大的差异体现在后量子解封装路径上——ML-KEM-768 和 ML-KEM-1024 快了 53–55%,X-Wing 解封装快了 38%,这是因为 `hpke-ng` 在 `PrivateKey` 中缓存了扩展的 FIPS 203 解封装密钥,而 `hpke-rs` 在每次 `setup_receiver` 时都从种子重新构建。将相同的思路应用于经典 KEM——将接收者的序列化公钥与密钥一起缓存——消除了每次解封装时冗余的基点标量乘法,将 X25519 解封装速度提升了 41%,X-Wing 封装提升了 14%,ML-KEM 封装提升了 30–37%。跨不同 payload 大小的单次打开(single-shot open)快了 23–35%,小于等于 16 KiB payload 的 AES-128-GCM 单次封装(single-shot seal)快了 6–12%,建立上下文后的 `Context::seal`(64 字节)快了 15%,1 KiB 的端到端往返快了 20%。 内存和二进制文件占用: | 数量 | hpke-rs | hpke-ng | |------------------------------------------------|-----------|----------------| | `Hpke` 结构体 | 320 bytes | **0 bytes** (`PhantomData`) | | `Context<_, _, ChaCha20Poly1305>` 结构体 | 400 bytes | **88 bytes** | | `Context<_, _, ExportOnly>` 结构体 | n/a | **56 bytes** | | `Context<_, _, Aes128Gcm>` 结构体 | 400 bytes | 792 bytes | | `Context<_, _, Aes256Gcm>` 结构体 | 400 bytes | 1,048 bytes | | 最小 Release 二进制文件 | 561 KB | **392 KB** (缩小约 30%) | AES-GCM 的 `Context` 行比 `hpke-rs` 大,因为密码的扩展轮密钥 + GHash 表被内联缓存了——这正是消除 `Context::seal` 中每次调用 AES 密钥调度开销的原因。使用 AES-GCM 的流式应用在此处是用内存换取吞吐量;ChaCha20-Poly1305 不受此影响。 在可用的情况下,使用 `RUSTFLAGS="-C target-cpu=native"` 构建,以启用 AES-NI / SHA-NI。`Cargo.toml` 中的 `[profile.bench]` 启用了 `lto = "thin"` 和 `codegen-units = 1`。要查看正面对比的数字,请在本地运行 `cargo bench --features comparative --bench comparative`;对比基准测试启用了 `hpke-rs-rust-crypto` 的 `experimental` feature,以便在 `hpke-rs` 侧连接后量子 KEM 桩代码。 ## 安全态势 该库针对先前实现中观察到的两类问题做出了应对: - **零共享密钥检查 (RFC 9180 §7.1.4)。** 使用 `subtle::ConstantTimeEq` 对 X25519 和 X448 强制执行。 - **Nonce 计数器回绕。** 从结构上预防:`Context` 使用 `u64` 序列号,当达到 `u64::MAX` 时拒绝加密,并且不可克隆,因此计数器无法产生分叉。 DH 后全零检查是常量时间的。`Context` 不能被 `Clone`,因此不能从同一上下文的两个副本在相同的 `(key, nonce)` 下生成两个密文。 ## 常量时间考量 此 crate 组合了 RustCrypto 的原语。常量时间特性继承自这些 crate: | 原语 | CT 特性 | |---|---| | X25519, X448 | 构造上即保证 CT。 | | P-256, P-384, P-521, secp256k1 | 在 `arithmetic` 模式下保证 CT (已固定)。 | | HKDF-SHA-{256,384,512} | 保证 CT (确定性的;无依赖于秘密数据的分支)。 | | ChaCha20-Poly1305 | 构造上即保证 CT。 | | AES-128-GCM, AES-256-GCM | **仅在有硬件 AES-NI/PCLMULQDQ 支持时保证 CT。** 在没有这些指令的平台上请优先使用 `ChaCha20Poly1305`。 | | ML-KEM, X-Wing | 根据上游文档保证 CT;这两个 crate 均处于 1.0 版本之前。 | ## 测试 ``` cargo test # library + roundtrip cargo test --features pq # + post-quantum tests cargo test --features pq,kat-internals # + RFC 9180 KAT cargo test --features pq,differential,kat-internals # + cross-impl differential vs hpke-rs ``` 测试覆盖范围包括跨越所有密码套件 × 模式组合的 59 个宏生成的往返测试,四个 `cargo-fuzz` 目标(panic 被视为 bug),以及针对 `hpke-rs` 的有线格式互操作差分测试。完整测试套件(不含差分测试)在两秒内即可运行完毕。 ## 从 `hpke-rs` 迁移 三个机械性步骤,对于真实的代码库通常不到一小时即可完成: 1. 定义一个 `type Suite = Hpke;` 别名,用于你正在使用的密码套件。 2. 将 `hpke.seal(...)` 调用替换为显式的模式方法:`Suite::seal_base`、`seal_psk`、`seal_auth` 或 `seal_auth_psk`。 3. 在调用处贯穿传递 `&mut rng`——配置不再拥有它。 有关具体的示例,请参阅[公告博文](https://symbolic.software/blog/2026-05-08-hpke-ng/)。 ## 许可证 根据您的选择,许可采用 [Apache License, Version 2.0](LICENSE-APACHE) 或 [MIT license](LICENSE-MIT)。
标签:ChaCha20Poly1305, HKDF, HPKE, RFC 9180, Rust, Web3基础设施, X25519, 加密库, 加密算法, 可视化界面, 安全通信, 密码学, 密钥封装, 手动系统调用, 无依赖注入, 混合公钥加密, 类型驱动开发, 网络安全, 网络安全, 网络流量审计, 通知系统, 隐私保护, 隐私保护, 零大小类型, 零开销抽象