许多工程师选择 Rust 作为实现加密协议的语言,因其有强大的安全保证。但仍有一些挑战需注意,如要保持常量时间属性以防止计时攻击,而编译器优化可能会破坏这些属性。
最近有客户询问将库打包为npm
模块并用wasm-pack
和node
运行会如何影响用 Rust 实现的常量时间加密代码。通过运行多个小测试案例来探索代码库两次优化对代码常量时间属性的影响。
常量时间相关
- 加密难正确实现,除担心总体正确性和边缘情况外,还需担心潜在的侧信道泄漏和计时攻击。
- 计时攻击是利用应用执行时间可能依赖输入的事实,通过控制流相关决策或内存读取位置来泄露秘密数据信息。
- 为防止计时差异,加密工程师通常避免基于秘密数据做决策,可通过常量时间实现来避免,如在 Rust 中使用巧妙技巧实现条件选择以避免计时差异。
与编译器的斗争
- 编译器无时间概念,可能会重写和优化常量时间代码,破坏其常量时间属性,即使是精心编写的代码也可能如此。
- 最明显的解决方案是关闭所有优化,但这不可行,因为加密代码需要编译器的优化。更吸引人的解决方案是使用优化屏障,如
[subtle](https://crates.io/crates/subtle)
crate 中的构造来阻止编译器优化敏感代码路径。但 Rust 目前没有严格定义的内存模型,core::ptr::read_volatile
的语义可能会随时间变化。
关于 Wasm
- 客户将 Rust 代码编译为 Wasm 并在
node
中运行,代码先由 LLVM 编译为 Wasm,再由node
的 Turbofan JIT 编译器再次编译。 - 测试
test_with_barrier
函数,发现插入优化屏障后函数能保持常量时间,但 Wasm 模块加载到node
中后会交给 Liftoff 和 Turbofan JIT 编译器进一步优化。 - 观察
black_box
函数在 Turbofan 中的输出,发现它未被内联或优化掉,但其将输入写入内存后返回的行为令人惊讶,这会导致秘密值被泄露到 Wasm 内存中,可从 JavaScript 读取。 - 讨论定义基于 C++优化屏障的新 Rust 内在函数,重新编译后
black_box
函数不再泄露秘密值,但不能期望 Turbofan 不会内联该函数。
未来展望
- 仅通过插入优化屏障来对抗 LLVM 不是提供常量时间保证的好方法,语言层面正在努力解决此问题,如
secret types RFC
和CT-Wasm 项目
,但缺少将秘密类型和相应语义引入 LLVM 的方法,若无 LLVM 支持,依赖 LLVM 的高级语言难以提供绝对的常量时间保证。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。