name: multiversx-constant-time description: 验证加密操作在恒定时间内执行以防止时序攻击。在审计自定义加密实现、秘密比较或智能合约中的安全敏感算法时使用。
恒定时间分析
验证加密秘密是否在恒定时间内处理,以防止时序攻击。此技能对于审查任何处理敏感数据的代码至关重要,其中执行时间可能泄露信息。
何时使用
- 审计自定义加密实现
- 审查秘密比较逻辑(哈希、签名、密钥)
- 分析认证或验证代码
- 检查密码/PIN处理
- 审查任何时序可能泄露秘密的代码
1. 理解时序攻击
威胁模型
攻击者测量操作所需时间来推断秘密值:
比较: secret[i] == input[i]
- 如果不匹配在 i=0: ~100ns (立即返回)
- 如果不匹配在 i=5: ~150ns (先检查5字节)
- 如果全部匹配: ~200ns (检查所有字节)
攻击: 尝试字节0的所有值,找到最快拒绝的 = 错误猜测
对每个字节位置重复
为什么在MultiversX上重要
- Gas计量可能泄露执行路径信息
- 跨分片时序差异可观测
- VM级优化可能改变执行时间
2. 避免的模式(可变时间)
早期退出比较
// 易受攻击: 早期退出泄露第一个不匹配的位置
fn compare_secrets(secret: &[u8], input: &[u8]) -> bool {
if secret.len() != input.len() {
return false; // 长度泄露!
}
for i in 0..secret.len() {
if secret[i] != input[i] {
return false; // 位置泄露!
}
}
true
}
短路布尔运算符
// 易受攻击: && 和 || 短路
fn verify_auth(token_valid: bool, signature_valid: bool) -> bool {
token_valid && signature_valid // 如果 token_valid 为假,签名不被检查
}
基于秘密的条件分支
// 易受攻击: 基于秘密值的不同代码路径
fn process_key(key: &[u8]) {
if key[0] == 0x00 {
// 快速路径
} else {
// 慢速路径,有更多操作
}
}
数据依赖的内存访问
// 易受攻击: 基于秘密值的缓存时序
fn lookup(secret_index: usize, table: &[u8]) -> u8 {
table[secret_index] // 缓存命中/未命中取决于 secret_index
}
3. MultiversX安全解决方案
使用VM加密函数
最佳实践: 总是优先使用内置VM加密操作:
// 正确: 使用VM提供的验证
fn verify_signature(&self, message: &ManagedBuffer, signature: &ManagedBuffer) {
let signer = self.expected_signer().get();
// 如果验证失败,用信号错误panic — 不返回布尔值
self.crypto().verify_ed25519(
signer.as_managed_buffer(),
message,
signature
);
}
// 正确: 使用VM提供的哈希
fn hash_data(&self, data: &ManagedBuffer) -> ManagedBuffer {
self.crypto().sha256(data)
}
ManagedBuffer比较
MultiversX VM的ManagedBuffer比较通常是恒定时间的:
// 正确: ManagedBuffer == 使用VM比较
fn verify_hash(&self, input_hash: &ManagedBuffer) -> bool {
let stored_hash = self.secret_hash().get();
stored_hash == *input_hash // VM处理比较
}
手动恒定时间比较(必要时)
如果必须比较原始字节:
// 正确: 恒定时间字节比较
fn constant_time_compare(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut result: u8 = 0;
for i in 0..a.len() {
result |= a[i] ^ b[i]; // 累积差异
}
result == 0 // 一次性检查所有
}
使用subtle板条箱
对于需要恒定时间操作的Rust代码:
use subtle::ConstantTimeEq;
fn verify_secret(stored: &[u8; 32], provided: &[u8; 32]) -> bool {
stored.ct_eq(provided).into() // 恒定时间比较
}
注意: 验证subtle板条箱与no_std和WASM兼容。
4. 验证技术
代码审查清单
- [ ] 没有基于秘密比较的早期返回
- [ ] 没有使用秘密依赖操作数的
&&或|| - [ ] 没有基于秘密值的分支(
if/match) - [ ] 没有使用秘密索引的数组索引
- [ ] 使用VM加密函数(如果可用)
静态分析模式
搜索潜在易受攻击的模式:
# 在比较类函数中查找早期返回
grep -n "return false" src/*.rs | grep -i "compare\|verify\|check"
# 查找敏感名称的短路运算符
grep -n "&&\|\\|\\|" src/*.rs | grep -i "secret\|key\|hash\|signature"
# 查找常见秘密变量名的条件分支
grep -n "if.*secret\|if.*key\|if.*hash" src/*.rs
Gas分析
在MultiversX上,Gas消耗可能指示时序:
// 检查Gas是否随输入变化
#[view]
fn gas_test(&self, input: ManagedBuffer) -> u64 {
let before = self.blockchain().get_gas_left();
// ... 要测试的操作 ...
let after = self.blockchain().get_gas_left();
before - after
}
警告: 这是近似的。真正的恒定时间需要VM级保证。
5. 常见易受攻击场景
认证令牌验证
// 易受攻击
fn verify_token(&self, token: &ManagedBuffer) -> bool {
let valid_token = self.auth_token().get();
let mut token_bytes = [0u8; 64];
let mut valid_bytes = [0u8; 64];
token.load_slice(0, &mut token_bytes[..token.len()]);
valid_token.load_slice(0, &mut valid_bytes[..valid_token.len()]);
for i in 0..token.len() {
if token_bytes[i] != valid_bytes[i] {
return false; // 时序泄露!
}
}
true
}
// 正确
fn verify_token(&self, token: &ManagedBuffer) -> bool {
let valid_token = self.auth_token().get();
valid_token == *token // ManagedBuffer相等性
}
HMAC验证
// 易受攻击: 在计算的HMAC上使用 ==
fn verify_hmac(&self, message: &ManagedBuffer, provided_mac: &ManagedBuffer) -> bool {
let computed_mac = self.compute_hmac(message);
computed_mac == *provided_mac // 可能可变时间!
}
// 正确: 使用VM加密或恒定时间比较
fn verify_hmac(&self, message: &ManagedBuffer, provided_mac: &ManagedBuffer) -> bool {
let computed_mac = self.compute_hmac(message);
self.constant_time_eq(&computed_mac, provided_mac)
}
密码/PIN比较
// 易受攻击
fn check_pin(&self, entered_pin: u32) -> bool {
entered_pin == self.stored_pin().get() // 比较可能短路
}
// 正确: 总是比较所有位
fn check_pin(&self, entered_pin: u32) -> bool {
let stored = self.stored_pin().get();
(entered_pin ^ stored) == 0 // XOR和检查
}
6. 审计报告模板
## 恒定时间分析
### 范围
审查的文件: [列表]
发现的加密操作: [计数]
### 发现
| 位置 | 操作 | 状态 | 备注 |
|----------|-----------|--------|-------|
| lib.rs:45 | 哈希比较 | 安全 | 使用 ManagedBuffer == |
| auth.rs:23 | 令牌验证 | 易受攻击 | 早期返回模式 |
| crypto.rs:89 | 签名 | 安全 | 使用 self.crypto() |
### 建议
1. [每个易受攻击位置的具体修复]
7. 关键原则
- 优先VM函数:
self.crypto().*方法被优化,可能是恒定时间的 - 避免DIY加密: 自定义实现很少必要且经常错误
- 假设时序泄露: 任何基于秘密的分支都是潜在漏洞
- 用Gas测试: Gas消耗可能揭示时序变化
- 文档假设: 注意哪些操作你假设是恒定时间的