安全加固:常量时间改进与侧信道缓解

- SM2: 常量时间标量乘法、点验证和域运算
- SM3: 常量时间填充和长度处理
- SM4: bitslice S-box 实现,避免缓存时序攻击
- SM4 模式: 常量时间 CBC 填充和标签比较
- SM9: 常量时间 Fp12 求逆和哈希到标量
- 添加 zeroize 用于私钥清理
- 改进错误处理,使用常量时间比较
This commit is contained in:
huangxt
2026-03-07 15:28:02 +08:00
parent e929e6a103
commit 7ec2580514
8 changed files with 412 additions and 158 deletions
+142 -60
View File
@@ -2,8 +2,8 @@
//!
//! # 安全说明
//!
//! S-box 使用**位切片(Bitslice)**实现,完全消除了缓存时序侧信道攻击面。
//! 每次 S-box 查询的访存模式与输入无关
//! S-box 使用**纯布尔电路位切片**实现(路径 A),完全消除内存访问,
//! 仅使用 AND/XOR/OR/NOT 位运算,无缓存时序侧信道攻击面
use zeroize::{Zeroize, ZeroizeOnDrop};
@@ -25,72 +25,154 @@ const CK: [u32; 32] = [
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279,
];
// ── 常量时间 S-box两级 4-bit 掩码查表)────────────────────────────────────
// ── 纯布尔电路 S-box路径 A:零内存访问位切片实现)─────────────────────────
//
// 将 8 位输入拆分为高 4 位(行索引)和低 4 位(列索引),
// 分两级各做 16 次掩码操作,合计 32 次掩码操作,远少于原 256 次。
// 仅使用 AND/XOR/OR/NOT 位运算,完全消除内存查表,无缓存时序侧信道。
//
// 安全性:
// - 无秘密依赖的条件分支
// - 无秘密索引的内存访问(每次访问固定的 16×16 = 256 字节区域)
// - 掩码生成仅用算术运算(XOR / wrapping_sub / 右移),无分支
// 算法来源:emmansun/sm4bssbox64 函数)经标量化提取并验证(256/256 全表正确)。
// 结构:输入线性层 -> GF(2^4) 求逆(top+middle 函数)-> 输出线性层(bottom+output 函数)
//
// Reason: 两级 4-bit 掩码查表是在不使用 SIMD 的前提下,兼顾性能与常量时间
// 安全性的务实方案(路径 B)。真正的布尔电路位切片(路径 A)记录于
// ROADMAP.md,待 v1.0 发布后评估。
// Reason: 纯布尔电路(路径 A)完全消除内存访问,不依赖缓存行为,
// 在所有微架构上均无侧信道风险。
/// SM4 S-box 常量时间查找:两级 4-bit 掩码(32 次掩码操作
/// SM4 S-box 布尔电路实现(路径 A
///
/// 将输入 `x` 拆分为高 4 位(行)和低 4 位(列),
/// - 第一级:16 次掩码操作选出正确的 16 字节行到栈上缓冲区
/// - 第二级:16 次掩码操作从缓冲区选出正确的 1 字节输出
///
/// 全程无条件分支,无秘密依赖的内存地址计算。
/// 仅使用 `&`/`^`/`|`/`!` 位运算,零内存访问,无条件分支。
/// 每个中间变量为 0 或 1(对应输入字节的各个位平面)。
#[inline]
pub(crate) fn sbox_ct(x: u8) -> u8 {
// SM4 S-box 按 16×16 组织(行 = 高半字节,列 = 低半字节
#[rustfmt::skip]
const SBOX: [[u8; 16]; 16] = [
[0xd6,0x90,0xe9,0xfe,0xcc,0xe1,0x3d,0xb7,0x16,0xb6,0x14,0xc2,0x28,0xfb,0x2c,0x05],
[0x2b,0x67,0x9a,0x76,0x2a,0xbe,0x04,0xc3,0xaa,0x44,0x13,0x26,0x49,0x86,0x06,0x99],
[0x9c,0x42,0x50,0xf4,0x91,0xef,0x98,0x7a,0x33,0x54,0x0b,0x43,0xed,0xcf,0xac,0x62],
[0xe4,0xb3,0x1c,0xa9,0xc9,0x08,0xe8,0x95,0x80,0xdf,0x94,0xfa,0x75,0x8f,0x3f,0xa6],
[0x47,0x07,0xa7,0xfc,0xf3,0x73,0x17,0xba,0x83,0x59,0x3c,0x19,0xe6,0x85,0x4f,0xa8],
[0x68,0x6b,0x81,0xb2,0x71,0x64,0xda,0x8b,0xf8,0xeb,0x0f,0x4b,0x70,0x56,0x9d,0x35],
[0x1e,0x24,0x0e,0x5e,0x63,0x58,0xd1,0xa2,0x25,0x22,0x7c,0x3b,0x01,0x21,0x78,0x87],
[0xd4,0x00,0x46,0x57,0x9f,0xd3,0x27,0x52,0x4c,0x36,0x02,0xe7,0xa0,0xc4,0xc8,0x9e],
[0xea,0xbf,0x8a,0xd2,0x40,0xc7,0x38,0xb5,0xa3,0xf7,0xf2,0xce,0xf9,0x61,0x15,0xa1],
[0xe0,0xae,0x5d,0xa4,0x9b,0x34,0x1a,0x55,0xad,0x93,0x32,0x30,0xf5,0x8c,0xb1,0xe3],
[0x1d,0xf6,0xe2,0x2e,0x82,0x66,0xca,0x60,0xc0,0x29,0x23,0xab,0x0d,0x53,0x4e,0x6f],
[0xd5,0xdb,0x37,0x45,0xde,0xfd,0x8e,0x2f,0x03,0xff,0x6a,0x72,0x6d,0x6c,0x5b,0x51],
[0x8d,0x1b,0xaf,0x92,0xbb,0xdd,0xbc,0x7f,0x11,0xd9,0x5c,0x41,0x1f,0x10,0x5a,0xd8],
[0x0a,0xc1,0x31,0x88,0xa5,0xcd,0x7b,0xbd,0x2d,0x74,0xd0,0x12,0xb8,0xe5,0xb4,0xb0],
[0x89,0x69,0x97,0x4a,0x0c,0x96,0x77,0x7e,0x65,0xb9,0xf1,0x09,0xc5,0x6e,0xc6,0x84],
[0x18,0xf0,0x7d,0xec,0x3a,0xdc,0x4d,0x20,0x79,0xee,0x5f,0x3e,0xd7,0xcb,0x39,0x48],
];
// 提取输入字节的 8 个位(b0 = LSB, b7 = MSB
let b0 = x & 1;
let b1 = (x >> 1) & 1;
let b2 = (x >> 2) & 1;
let b3 = (x >> 3) & 1;
let b4 = (x >> 4) & 1;
let b5 = (x >> 5) & 1;
let b6 = (x >> 6) & 1;
let b7 = (x >> 7) & 1;
let hi = (x >> 4) as usize; // 行索引(高 4 位)
let lo = (x & 0xF) as usize; // 列索引(低 4 位)
// ── 输入线性层(input function)──────────────────────────────────────────
// Reason: 将输入 8 位映射为中间变量 g0..g7, m0..m9,为 GF(2^4) 求逆做准备。
let t1 = b7 ^ b5;
let t2 = 1 ^ (b5 ^ b1); // NOT(b5 ^ b1) = g4
let g5 = 1 ^ b0; // NOT(b0)
let t3 = 1 ^ (b0 ^ t2); // NOT(b0 ^ t2) = m1
let t4 = b6 ^ b2; // m4
let t5 = b3 ^ t3; // g3
let t6 = b4 ^ t1; // m0
let t7 = b1 ^ t5; // g1
let t8 = b1 ^ t4; // m2
let t9 = t6 ^ t8; // m8
let t10 = t6 ^ t7; // g0
let t11 = 1 ^ (b3 ^ t1); // NOT(b3 ^ t1) = m5
let t12 = 1 ^ (b6 ^ t9); // NOT(b6 ^ t9) = m9
// 第一级:16 次掩码操作,选出行 hi 的 16 字节到栈缓冲区
// Reason: mask = 0xFF 当且仅当 i == hi(无分支算术),遍历全部 16 行
// 保证访问模式与 hi 无关。
let mut row = [0u8; 16];
for (i, srow) in SBOX.iter().enumerate() {
let mask = ((hi ^ i).wrapping_sub(1) >> 8) as u8;
for (j, &v) in srow.iter().enumerate() {
row[j] |= v & mask;
}
}
let g0 = t10;
let g1 = t7;
let g2 = t4 ^ t10;
let g3 = t5;
let g4 = t2;
let g6 = t11 ^ t2;
let g7 = t12 ^ (t11 ^ t2);
let m0 = t6;
let m1 = t3;
let m2 = t8;
let m3 = t3 ^ t12;
let m4 = t4;
let m5 = t11;
let m6 = b1;
let m7 = t11 ^ m3;
let m8 = t9;
let m9 = t12;
// 第二级:16 次掩码操作,从缓冲区选出列 lo 的字节
// Reason: 同上,访问模式与 lo 无关
let mut result = 0u8;
for (j, &v) in row.iter().enumerate() {
let mask = ((lo ^ j).wrapping_sub(1) >> 8) as u8;
result |= v & mask;
}
result
// ── Top 函数(GF(2^4) 求逆的输入准备)────────────────────────────────────
// Reason: 将 16 个中间变量组合为 p0..p3,供 GF(2^2) 中间层使用
let t2t = m0 & m1;
let t3t = g0 & g4;
let t4t = g3 & g7;
let t7t = g3 | g7;
let t11t = m4 & m5;
let t10t = m3 & m2;
let t12t = m3 | m2;
let t6t = g6 | g2;
let t9t = m6 | m7;
let t5t = m8 & m9;
let t8t = m8 | m9;
let t14t = t3t ^ t2t;
let t16t = t5t ^ t14t;
let t20t = t16t ^ t7t;
let t17t = t9t ^ t10t;
let t18t = t11t ^ t12t;
let p2 = t20t ^ t18t;
let p0 = t6t ^ t16t;
let t1t = g5 & g1;
let t13t = t1t ^ t2t;
let t15t = t13t ^ t4t;
let p3 = (t6t ^ t15t) ^ t17t;
let p1 = t8t ^ t15t;
// ── Middle 函数(GF(2^2) 求逆)───────────────────────────────────────────
// Reason: 在 GF(2^2) 上对 (p0,p1,p2,p3) 组成的元素进行求逆,输出 l0..l3。
let t0m = p1 & p2;
let t1m = p3 & p0;
let t2m = p0 & p2;
let t3m = p1 & p3;
let t4m = t0m & t2m;
let t5m = t1m ^ t3m;
let t6m = t5m | p0;
let t7m = t2m | p3;
let l3 = t4m ^ t6m;
let t9m = t7m ^ t3m;
let l0 = t0m ^ t9m;
let t11m = p2 | t5m;
let l1 = t11m ^ t1m;
let t12m = p1 | t2m;
let l2 = t12m ^ t5m;
// ── Bottom 函数(GF(2^4) 求逆的输出组合)─────────────────────────────────
// Reason: 将 l0..l3 与输入中间变量结合,得到 r0..r11(12 个中间结果)。
let k4 = l2 ^ l3;
let k3 = l1 ^ l3;
let k2 = l0 ^ l2;
let k0 = l0 ^ l1;
let k1 = k2 ^ k3;
let e0 = m1 & k0; let e1 = g5 & l1; let r0 = e0 ^ e1;
let e2 = g4 & l0; let r1 = e2 ^ e1;
let e3 = m7 & k3; let e4 = m5 & k2; let r2 = e3 ^ e4;
let e5 = m3 & k1; let r3 = e5 ^ e4;
let e6 = m9 & k4; let e7 = g7 & l3; let r4 = e6 ^ e7;
let e8 = g6 & l2; let r5 = e8 ^ e7;
let e9 = m0 & k0; let e10 = g1 & l1; let r6 = e9 ^ e10;
let e11 = g0 & l0; let r7 = e11 ^ e10;
let e12 = m6 & k3; let e13 = m4 & k2; let r8 = e12 ^ e13;
let e14 = m2 & k1; let r9 = e14 ^ e13;
let e15 = m8 & k4; let e16 = g3 & l3; let r10 = e15 ^ e16;
let e17 = g2 & l2; let r11 = e17 ^ e16;
// ── 输出线性层(output function)──────────────────────────────────────────
// Reason: 将 r0..r11 组合为输出字节的 8 个位。
let t1o = r7 ^ r9;
let t2o = r1 ^ t1o;
let t3o = r3 ^ t2o;
let t4o = r5 ^ r3;
let t5o = r4 ^ t4o;
let t6o = r0 ^ r4;
let t7o = r11 ^ r7;
let b5o = t1o ^ t4o;
let b2o = t1o ^ t6o;
let t10o = r2 ^ t5o;
let b3o = r10 ^ r8;
let b1o = 1 ^ (t3o ^ b3o);
let b6o = t10o ^ b1o;
let b4o = 1 ^ (t3o ^ t7o);
let b0o = t6o ^ b4o;
let b7o = 1 ^ (r10 ^ r6);
// 将 8 个输出位重组为字节
b0o | (b1o << 1) | (b2o << 2) | (b3o << 3)
| (b4o << 4) | (b5o << 5) | (b6o << 6) | (b7o << 7)
}
/// SM4 τ 变换:对 u32 的 4 个字节分别做 S-box(常量时间)
@@ -288,7 +370,7 @@ mod tests {
assert_eq!(block, plain, "SM4 加解密往返不一致");
}
/// 常量时间 S-box 与标准 S-box 表一致性验证
/// 布尔电路 S-box 与标准 S-box 表一致性验证256 点全表)
#[test]
fn test_sbox_ct_correct() {
#[rustfmt::skip]
@@ -314,7 +396,7 @@ mod tests {
assert_eq!(
sbox_ct(i),
REF[i as usize],
"S-box 常量时间实现在输入 {i:#04x} 处与标准不一致"
"S-box 布尔电路实现在输入 {i:#04x} 处与标准不一致"
);
}
}