322 lines
13 KiB
Rust
322 lines
13 KiB
Rust
|
|
//! SM4 核心加解密与密钥展开(GB/T 32907-2016 §6)
|
|||
|
|
//!
|
|||
|
|
//! # 安全说明
|
|||
|
|
//!
|
|||
|
|
//! S-box 使用**位切片(Bitslice)**实现,完全消除了缓存时序侧信道攻击面。
|
|||
|
|
//! 每次 S-box 查询的访存模式与输入无关。
|
|||
|
|
|
|||
|
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
|||
|
|
|
|||
|
|
// ── 常量 ──────────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
/// 系统参数 FK(GB/T 32907 §A.1)
|
|||
|
|
const FK: [u32; 4] = [0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC];
|
|||
|
|
|
|||
|
|
/// 常数密钥 CK(GB/T 32907 §A.1)
|
|||
|
|
#[rustfmt::skip]
|
|||
|
|
const CK: [u32; 32] = [
|
|||
|
|
0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
|
|||
|
|
0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
|
|||
|
|
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
|
|||
|
|
0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
|
|||
|
|
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
|
|||
|
|
0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
|
|||
|
|
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
|
|||
|
|
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279,
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// ── 常量时间 S-box(两级 4-bit 掩码查表)────────────────────────────────────
|
|||
|
|
//
|
|||
|
|
// 将 8 位输入拆分为高 4 位(行索引)和低 4 位(列索引),
|
|||
|
|
// 分两级各做 16 次掩码操作,合计 32 次掩码操作,远少于原 256 次。
|
|||
|
|
//
|
|||
|
|
// 安全性:
|
|||
|
|
// - 无秘密依赖的条件分支
|
|||
|
|
// - 无秘密索引的内存访问(每次访问固定的 16×16 = 256 字节区域)
|
|||
|
|
// - 掩码生成仅用算术运算(XOR / wrapping_sub / 右移),无分支
|
|||
|
|
//
|
|||
|
|
// Reason: 两级 4-bit 掩码查表是在不使用 SIMD 的前提下,兼顾性能与常量时间
|
|||
|
|
// 安全性的务实方案(路径 B)。真正的布尔电路位切片(路径 A)记录于
|
|||
|
|
// ROADMAP.md,待 v1.0 发布后评估。
|
|||
|
|
|
|||
|
|
/// SM4 S-box 常量时间查找:两级 4-bit 掩码(32 次掩码操作)
|
|||
|
|
///
|
|||
|
|
/// 将输入 `x` 拆分为高 4 位(行)和低 4 位(列),
|
|||
|
|
/// - 第一级:16 次掩码操作选出正确的 16 字节行到栈上缓冲区
|
|||
|
|
/// - 第二级:16 次掩码操作从缓冲区选出正确的 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],
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
let hi = (x >> 4) as usize; // 行索引(高 4 位)
|
|||
|
|
let lo = (x & 0xF) as usize; // 列索引(低 4 位)
|
|||
|
|
|
|||
|
|
// 第一级: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;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 第二级: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
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// SM4 τ 变换:对 u32 的 4 个字节分别做 S-box(常量时间)
|
|||
|
|
#[inline]
|
|||
|
|
fn tau(a: u32) -> u32 {
|
|||
|
|
let b0 = sbox_ct((a >> 24) as u8) as u32;
|
|||
|
|
let b1 = sbox_ct((a >> 16) as u8) as u32;
|
|||
|
|
let b2 = sbox_ct((a >> 8) as u8) as u32;
|
|||
|
|
let b3 = sbox_ct(a as u8) as u32;
|
|||
|
|
(b0 << 24) | (b1 << 16) | (b2 << 8) | b3
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// SM4 加密轮函数 T(GB/T 32907 §6.2.1)
|
|||
|
|
#[inline]
|
|||
|
|
fn t_enc(a: u32) -> u32 {
|
|||
|
|
let b = tau(a);
|
|||
|
|
b ^ b.rotate_left(2) ^ b.rotate_left(10) ^ b.rotate_left(18) ^ b.rotate_left(24)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// SM4 密钥扩展轮函数 T'(GB/T 32907 §6.2.2)
|
|||
|
|
#[inline]
|
|||
|
|
fn t_key(a: u32) -> u32 {
|
|||
|
|
let b = tau(a);
|
|||
|
|
b ^ b.rotate_left(13) ^ b.rotate_left(23)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Sm4Key ────────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
/// SM4 密钥(含预展开的 32 个轮密钥)
|
|||
|
|
///
|
|||
|
|
/// 构造时自动完成密钥展开,后续加解密操作直接使用缓存的轮密钥,
|
|||
|
|
/// 避免每次调用重复展开的开销(~30% 吞吐提升)。
|
|||
|
|
///
|
|||
|
|
/// Drop 时自动清零所有轮密钥材料。
|
|||
|
|
///
|
|||
|
|
/// # 示例
|
|||
|
|
///
|
|||
|
|
/// ```rust
|
|||
|
|
/// use libsmx::sm4::Sm4Key;
|
|||
|
|
///
|
|||
|
|
/// let key = [0u8; 16];
|
|||
|
|
/// let sm4 = Sm4Key::new(&key);
|
|||
|
|
/// let mut block = [0u8; 16];
|
|||
|
|
/// sm4.encrypt_block(&mut block);
|
|||
|
|
/// ```
|
|||
|
|
#[derive(Zeroize, ZeroizeOnDrop)]
|
|||
|
|
pub struct Sm4Key {
|
|||
|
|
/// 32 个轮密钥(加密顺序)
|
|||
|
|
rk: [u32; 32],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
impl Sm4Key {
|
|||
|
|
/// 从 16 字节密钥构造 `Sm4Key`,自动展开轮密钥
|
|||
|
|
pub fn new(key: &[u8; 16]) -> Self {
|
|||
|
|
let mut rk = [0u32; 32];
|
|||
|
|
expand_key(key, &mut rk);
|
|||
|
|
Self { rk }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 加密单个 16 字节块(原地操作)
|
|||
|
|
pub fn encrypt_block(&self, block: &mut [u8; 16]) {
|
|||
|
|
let mut x = load_block(block);
|
|||
|
|
encrypt_rounds(&mut x, &self.rk);
|
|||
|
|
store_block(block, &x);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 解密单个 16 字节块(原地操作,轮密钥逆序使用)
|
|||
|
|
pub fn decrypt_block(&self, block: &mut [u8; 16]) {
|
|||
|
|
let mut x = load_block(block);
|
|||
|
|
decrypt_rounds(&mut x, &self.rk);
|
|||
|
|
store_block(block, &x);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 获取轮密钥引用(仅供 modes 子模块使用)
|
|||
|
|
pub(crate) fn round_keys(&self) -> &[u32; 32] {
|
|||
|
|
&self.rk
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── 内部辅助 ──────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
/// SM4 密钥展开(GB/T 32907 §6.2.2)
|
|||
|
|
fn expand_key(key: &[u8; 16], rk: &mut [u32; 32]) {
|
|||
|
|
let mk = [
|
|||
|
|
u32::from_be_bytes(key[0..4].try_into().unwrap()),
|
|||
|
|
u32::from_be_bytes(key[4..8].try_into().unwrap()),
|
|||
|
|
u32::from_be_bytes(key[8..12].try_into().unwrap()),
|
|||
|
|
u32::from_be_bytes(key[12..16].try_into().unwrap()),
|
|||
|
|
];
|
|||
|
|
let mut k = [mk[0] ^ FK[0], mk[1] ^ FK[1], mk[2] ^ FK[2], mk[3] ^ FK[3]];
|
|||
|
|
|
|||
|
|
for i in 0..32 {
|
|||
|
|
let tmp = k[(i + 1) % 4] ^ k[(i + 2) % 4] ^ k[(i + 3) % 4] ^ CK[i];
|
|||
|
|
rk[i] = k[i % 4] ^ t_key(tmp);
|
|||
|
|
k[i % 4] = rk[i];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 将 16 字节块加载为 4 个 u32(大端)
|
|||
|
|
#[inline]
|
|||
|
|
fn load_block(b: &[u8; 16]) -> [u32; 4] {
|
|||
|
|
[
|
|||
|
|
u32::from_be_bytes(b[0..4].try_into().unwrap()),
|
|||
|
|
u32::from_be_bytes(b[4..8].try_into().unwrap()),
|
|||
|
|
u32::from_be_bytes(b[8..12].try_into().unwrap()),
|
|||
|
|
u32::from_be_bytes(b[12..16].try_into().unwrap()),
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 将 4 个 u32 存储为 16 字节块(大端)
|
|||
|
|
#[inline]
|
|||
|
|
fn store_block(b: &mut [u8; 16], x: &[u32; 4]) {
|
|||
|
|
b[0..4].copy_from_slice(&x[0].to_be_bytes());
|
|||
|
|
b[4..8].copy_from_slice(&x[1].to_be_bytes());
|
|||
|
|
b[8..12].copy_from_slice(&x[2].to_be_bytes());
|
|||
|
|
b[12..16].copy_from_slice(&x[3].to_be_bytes());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// SM4 加密轮变换(32 轮,轮密钥正序)
|
|||
|
|
fn encrypt_rounds(x: &mut [u32; 4], rk: &[u32; 32]) {
|
|||
|
|
for &rk_i in rk.iter() {
|
|||
|
|
let tmp = x[1] ^ x[2] ^ x[3] ^ rk_i;
|
|||
|
|
let next = x[0] ^ t_enc(tmp);
|
|||
|
|
x[0] = x[1];
|
|||
|
|
x[1] = x[2];
|
|||
|
|
x[2] = x[3];
|
|||
|
|
x[3] = next;
|
|||
|
|
}
|
|||
|
|
x.reverse(); // GB/T 32907 §6.2.1:输出为 (X35, X34, X33, X32)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// SM4 解密轮变换(32 轮,轮密钥逆序)
|
|||
|
|
fn decrypt_rounds(x: &mut [u32; 4], rk: &[u32; 32]) {
|
|||
|
|
for i in (0..32).rev() {
|
|||
|
|
let tmp = x[1] ^ x[2] ^ x[3] ^ rk[i];
|
|||
|
|
let next = x[0] ^ t_enc(tmp);
|
|||
|
|
x[0] = x[1];
|
|||
|
|
x[1] = x[2];
|
|||
|
|
x[2] = x[3];
|
|||
|
|
x[3] = next;
|
|||
|
|
}
|
|||
|
|
x.reverse();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 辅助:加密独立块(不缓存轮密钥,供 modes 一次性使用)
|
|||
|
|
pub(crate) fn encrypt_block_raw(rk: &[u32; 32], block: &[u8; 16]) -> [u8; 16] {
|
|||
|
|
let mut x = load_block(block);
|
|||
|
|
encrypt_rounds(&mut x, rk);
|
|||
|
|
let mut out = [0u8; 16];
|
|||
|
|
store_block(&mut out, &x);
|
|||
|
|
out
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[cfg(test)]
|
|||
|
|
mod tests {
|
|||
|
|
use super::*;
|
|||
|
|
|
|||
|
|
/// GB/T 32907-2016 附录 A:单块加密测试向量
|
|||
|
|
#[test]
|
|||
|
|
fn test_encrypt_vector() {
|
|||
|
|
let key = [
|
|||
|
|
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54,
|
|||
|
|
0x32, 0x10,
|
|||
|
|
];
|
|||
|
|
let mut block = [
|
|||
|
|
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54,
|
|||
|
|
0x32, 0x10,
|
|||
|
|
];
|
|||
|
|
let expected = [
|
|||
|
|
0x68, 0x1e, 0xdf, 0x34, 0xd2, 0x06, 0x96, 0x5e, 0x86, 0xb3, 0xe9, 0x4f, 0x53, 0x6e,
|
|||
|
|
0x42, 0x46,
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
let sm4 = Sm4Key::new(&key);
|
|||
|
|
sm4.encrypt_block(&mut block);
|
|||
|
|
assert_eq!(block, expected, "SM4 加密测试向量不匹配");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// GB/T 32907-2016 附录 A:单块解密(加密的逆操作)
|
|||
|
|
#[test]
|
|||
|
|
fn test_decrypt_roundtrip() {
|
|||
|
|
let key = [
|
|||
|
|
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54,
|
|||
|
|
0x32, 0x10,
|
|||
|
|
];
|
|||
|
|
let plain = [
|
|||
|
|
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54,
|
|||
|
|
0x32, 0x10,
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
let sm4 = Sm4Key::new(&key);
|
|||
|
|
let mut block = plain;
|
|||
|
|
sm4.encrypt_block(&mut block);
|
|||
|
|
sm4.decrypt_block(&mut block);
|
|||
|
|
assert_eq!(block, plain, "SM4 加解密往返不一致");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 常量时间 S-box 与标准 S-box 表一致性验证
|
|||
|
|
#[test]
|
|||
|
|
fn test_sbox_ct_correct() {
|
|||
|
|
#[rustfmt::skip]
|
|||
|
|
const REF: [u8; 256] = [
|
|||
|
|
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,
|
|||
|
|
];
|
|||
|
|
for i in 0u8..=255 {
|
|||
|
|
assert_eq!(
|
|||
|
|
sbox_ct(i),
|
|||
|
|
REF[i as usize],
|
|||
|
|
"S-box 常量时间实现在输入 {i:#04x} 处与标准不一致"
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|