初始提交:SM2/SM3/SM4/SM9 密码算法库

- SM3 哈希函数 (GB/T 32905-2013)
- SM4 分组密码,支持 ECB/CBC/OFB/CFB/CTR/GCM/CCM/XTS 模式 (GB/T 32907-2016)
- SM2 椭圆曲线密码 (GB/T 32918.1-5-2016)
- SM9 标识密码 (GB/T 38635.1-2-2020)
- 全程常量时间运算
- 支持 no_std,带 alloc 特性
- 完整的国标测试向量
- Criterion 性能基准测试
This commit is contained in:
huangxt
2026-03-07 13:03:10 +08:00
commit e929e6a103
34 changed files with 7530 additions and 0 deletions
+321
View File
@@ -0,0 +1,321 @@
//! SM4 核心加解密与密钥展开(GB/T 32907-2016 §6
//!
//! # 安全说明
//!
//! S-box 使用**位切片(Bitslice)**实现,完全消除了缓存时序侧信道攻击面。
//! 每次 S-box 查询的访存模式与输入无关。
use zeroize::{Zeroize, ZeroizeOnDrop};
// ── 常量 ──────────────────────────────────────────────────────────────────────
/// 系统参数 FKGB/T 32907 §A.1
const FK: [u32; 4] = [0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC];
/// 常数密钥 CKGB/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 加密轮函数 TGB/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} 处与标准不一致"
);
}
}
}
+8
View File
@@ -0,0 +1,8 @@
//! SM4 分组密码(GB/T 32907-2016
//! 实现见各子模块。
mod cipher;
mod modes;
pub use cipher::Sm4Key;
pub use modes::*;
+689
View File
@@ -0,0 +1,689 @@
//! SM4 分组模式(GB/T 32907-2016GB/T 17964-2021
//!
//! 支持:ECB、CBC、OFB、CFB、CTR、GCMAEAD)、CCMAEAD)、XTS
//!
//! # 安全说明
//!
//! - GCM/CCM 认证标签比较使用 `subtle::ConstantTimeEq`,防止时序侧信道
//! - CCM 严格遵循"先验证后解密"原则(Encrypt-then-MAC 的接收端验证)
//! - 所有密钥材料通过 [`Sm4Key`] 在 Drop 时自动清零
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use subtle::ConstantTimeEq;
use super::cipher::{encrypt_block_raw, Sm4Key};
// ── ECB ──────────────────────────────────────────────────────────────────────
/// SM4-ECB 加密(无填充,`data` 必须为 16 字节整倍数)
///
/// # 参数
/// - `key`: 16 字节密钥
/// - `data`: 明文(长度须为 16 的倍数)
///
/// # 返回
/// 密文字节向量
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_ecb(key: &[u8; 16], data: &[u8]) -> Vec<u8> {
let sm4 = Sm4Key::new(key);
data.chunks(16)
.flat_map(|chunk| {
let mut block = [0u8; 16];
block[..chunk.len()].copy_from_slice(chunk);
sm4.encrypt_block(&mut block);
block
})
.collect()
}
/// SM4-ECB 解密(无填充,`data` 必须为 16 字节整倍数)
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_ecb(key: &[u8; 16], data: &[u8]) -> Vec<u8> {
let sm4 = Sm4Key::new(key);
data.chunks(16)
.flat_map(|chunk| {
let mut block = [0u8; 16];
block[..chunk.len()].copy_from_slice(chunk);
sm4.decrypt_block(&mut block);
block
})
.collect()
}
// ── CBC ──────────────────────────────────────────────────────────────────────
/// SM4-CBC 加密(`plaintext.len()` 须为 16 字节整倍数)
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_cbc(key: &[u8; 16], iv: &[u8; 16], plaintext: &[u8]) -> Vec<u8> {
let sm4 = Sm4Key::new(key);
let mut prev = *iv;
plaintext
.chunks(16)
.flat_map(|chunk| {
let mut block = [0u8; 16];
let len = chunk.len().min(16);
block[..len].copy_from_slice(&chunk[..len]);
for i in 0..16 {
block[i] ^= prev[i];
}
sm4.encrypt_block(&mut block);
prev = block;
block
})
.collect()
}
/// SM4-CBC 解密(`ciphertext.len()` 须为 16 字节整倍数)
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_cbc(key: &[u8; 16], iv: &[u8; 16], ciphertext: &[u8]) -> Vec<u8> {
let sm4 = Sm4Key::new(key);
let mut prev = *iv;
ciphertext
.chunks(16)
.flat_map(|chunk| {
let mut block = [0u8; 16];
block[..chunk.len()].copy_from_slice(chunk);
let ct = block;
sm4.decrypt_block(&mut block);
for i in 0..16 {
block[i] ^= prev[i];
}
prev = ct;
block
})
.collect()
}
// ── OFB ──────────────────────────────────────────────────────────────────────
/// SM4-OFB 加密/解密(自反模式,加解密逻辑相同)
#[cfg(feature = "alloc")]
pub fn sm4_crypt_ofb(key: &[u8; 16], iv: &[u8; 16], data: &[u8]) -> Vec<u8> {
let sm4 = Sm4Key::new(key);
let mut feedback = *iv;
let mut out = Vec::with_capacity(data.len());
for chunk in data.chunks(16) {
sm4.encrypt_block(&mut feedback);
for (i, &b) in chunk.iter().enumerate() {
out.push(b ^ feedback[i]);
}
}
out
}
// ── CFB ──────────────────────────────────────────────────────────────────────
/// SM4-CFB 加密
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_cfb(key: &[u8; 16], iv: &[u8; 16], data: &[u8]) -> Vec<u8> {
let sm4 = Sm4Key::new(key);
let mut feedback = *iv;
let mut out = Vec::with_capacity(data.len());
for chunk in data.chunks(16) {
let mut ks = feedback;
sm4.encrypt_block(&mut ks);
let mut ct_block = [0u8; 16];
for (i, &b) in chunk.iter().enumerate() {
ct_block[i] = b ^ ks[i];
}
feedback = ct_block;
out.extend_from_slice(&ct_block[..chunk.len()]);
}
out
}
/// SM4-CFB 解密
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_cfb(key: &[u8; 16], iv: &[u8; 16], data: &[u8]) -> Vec<u8> {
let sm4 = Sm4Key::new(key);
let mut feedback = *iv;
let mut out = Vec::with_capacity(data.len());
for chunk in data.chunks(16) {
let mut ks = feedback;
sm4.encrypt_block(&mut ks);
let mut ct_block = [0u8; 16];
ct_block[..chunk.len()].copy_from_slice(chunk);
// Reason: CFB 解密中 feedback 使用密文块,而非明文块
feedback = ct_block;
for (i, &b) in chunk.iter().enumerate() {
out.push(b ^ ks[i]);
}
}
out
}
// ── CTR ──────────────────────────────────────────────────────────────────────
/// CTR 计数器递增(全 128 位大端)
#[inline]
fn ctr_inc(counter: &mut [u8; 16]) {
for i in (0..16).rev() {
counter[i] = counter[i].wrapping_add(1);
if counter[i] != 0 {
break;
}
}
}
/// SM4-CTR 加密/解密(自反模式)
#[cfg(feature = "alloc")]
pub fn sm4_crypt_ctr(key: &[u8; 16], nonce: &[u8; 16], data: &[u8]) -> Vec<u8> {
let sm4 = Sm4Key::new(key);
let mut counter = *nonce;
let mut out = Vec::with_capacity(data.len());
for chunk in data.chunks(16) {
let mut ks = counter;
sm4.encrypt_block(&mut ks);
for (i, &b) in chunk.iter().enumerate() {
out.push(b ^ ks[i]);
}
ctr_inc(&mut counter);
}
out
}
// ── GCM ──────────────────────────────────────────────────────────────────────
/// GF(2^128) 乘法(NIST SP 800-38D Algorithm 1
/// Reason: GHASH 的核心运算,不可约多项式 x^128 + x^7 + x^2 + x + 1
fn gf128_mul(x: &[u8; 16], y: &[u8; 16]) -> [u8; 16] {
let mut z = [0u8; 16];
let mut v = *y;
for byte_xi in x.iter() {
for bit_idx in (0..8).rev() {
if (byte_xi >> bit_idx) & 1 == 1 {
for j in 0..16 {
z[j] ^= v[j];
}
}
let lsb = v[15] & 1;
for j in (1..16).rev() {
v[j] = (v[j] >> 1) | (v[j - 1] << 7);
}
v[0] >>= 1;
if lsb == 1 {
v[0] ^= 0xE1;
}
}
}
z
}
/// GHASH 认证函数(NIST SP 800-38D §6.4
fn ghash(h: &[u8; 16], aad: &[u8], ciphertext: &[u8]) -> [u8; 16] {
let mut y = [0u8; 16];
for chunk in aad.chunks(16) {
let mut block = [0u8; 16];
block[..chunk.len()].copy_from_slice(chunk);
for i in 0..16 {
y[i] ^= block[i];
}
y = gf128_mul(&y, h);
}
for chunk in ciphertext.chunks(16) {
let mut block = [0u8; 16];
block[..chunk.len()].copy_from_slice(chunk);
for i in 0..16 {
y[i] ^= block[i];
}
y = gf128_mul(&y, h);
}
let mut len_block = [0u8; 16];
len_block[0..8].copy_from_slice(&((aad.len() as u64) * 8).to_be_bytes());
len_block[8..16].copy_from_slice(&((ciphertext.len() as u64) * 8).to_be_bytes());
for i in 0..16 {
y[i] ^= len_block[i];
}
gf128_mul(&y, h)
}
/// GCM 计数器递增(仅最后 4 字节,GCM 标准)
#[inline]
fn gcm_ctr_inc(counter: &mut [u8; 16]) {
// Reason: GCM 规范中 J0 的计数器字段只占最后 4 字节(大端 32 位)
for i in (12..16).rev() {
counter[i] = counter[i].wrapping_add(1);
if counter[i] != 0 {
break;
}
}
}
/// SM4-GCM 加密(AEAD
///
/// # 参数
/// - `key`: 16 字节密钥
/// - `nonce`: 12 字节 nonceGCM 标准推荐)
/// - `aad`: 附加认证数据(不加密,但参与认证)
/// - `plaintext`: 明文
///
/// # 返回
/// `(密文, 16字节认证标签)`
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_gcm(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
plaintext: &[u8],
) -> (Vec<u8>, [u8; 16]) {
let sm4 = Sm4Key::new(key);
let rk = sm4.round_keys();
let h = encrypt_block_raw(rk, &[0u8; 16]);
let mut j0 = [0u8; 16];
j0[..12].copy_from_slice(nonce);
j0[15] = 1;
let mut ctr = j0;
gcm_ctr_inc(&mut ctr);
let ciphertext: Vec<u8> = {
let mut out = Vec::with_capacity(plaintext.len());
let mut counter = ctr;
for chunk in plaintext.chunks(16) {
let ks = encrypt_block_raw(rk, &counter);
for (i, &b) in chunk.iter().enumerate() {
out.push(b ^ ks[i]);
}
gcm_ctr_inc(&mut counter);
}
out
};
let ghash_val = ghash(&h, aad, &ciphertext);
let ej0 = encrypt_block_raw(rk, &j0);
let mut tag = [0u8; 16];
for i in 0..16 {
tag[i] = ghash_val[i] ^ ej0[i];
}
(ciphertext, tag)
}
/// SM4-GCM 解密(AEAD
///
/// **先验证认证标签,验证通过后才解密。**
///
/// # 错误
/// 返回 [`crate::error::Error::AuthTagMismatch`] 当标签验证失败。
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_gcm(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
ciphertext: &[u8],
tag: &[u8; 16],
) -> Result<Vec<u8>, crate::error::Error> {
let sm4 = Sm4Key::new(key);
let rk = sm4.round_keys();
let h = encrypt_block_raw(rk, &[0u8; 16]);
let mut j0 = [0u8; 16];
j0[..12].copy_from_slice(nonce);
j0[15] = 1;
// Reason: 先验证 tag 再解密,防止 padding oracle 和选择密文攻击
let ghash_val = ghash(&h, aad, ciphertext);
let ej0 = encrypt_block_raw(rk, &j0);
let mut expected_tag = [0u8; 16];
for i in 0..16 {
expected_tag[i] = ghash_val[i] ^ ej0[i];
}
// 常量时间 tag 比较,防止时序侧信道
if expected_tag.ct_eq(tag).unwrap_u8() == 0 {
return Err(crate::error::Error::AuthTagMismatch);
}
let mut ctr = j0;
gcm_ctr_inc(&mut ctr);
let mut plaintext = Vec::with_capacity(ciphertext.len());
let mut counter = ctr;
for chunk in ciphertext.chunks(16) {
let ks = encrypt_block_raw(rk, &counter);
for (i, &b) in chunk.iter().enumerate() {
plaintext.push(b ^ ks[i]);
}
gcm_ctr_inc(&mut counter);
}
Ok(plaintext)
}
// ── CCM ──────────────────────────────────────────────────────────────────────
/// 构造 CCM CBC-MACRFC 3610
fn ccm_cbc_mac(
rk: &[u32; 32],
nonce: &[u8; 12],
aad: &[u8],
message: &[u8],
tag_len: usize,
) -> [u8; 16] {
let q = 3usize; // nonce=12B 时 q=15-12=3
let has_aad = !aad.is_empty();
let flags = ((has_aad as u8) << 6) | (((tag_len - 2) / 2) as u8) << 3 | (q as u8 - 1);
let mut b0 = [0u8; 16];
b0[0] = flags;
b0[1..13].copy_from_slice(nonce);
let msg_len = message.len() as u32;
b0[13] = (msg_len >> 16) as u8;
b0[14] = (msg_len >> 8) as u8;
b0[15] = msg_len as u8;
let mut x = encrypt_block_raw(rk, &b0);
if has_aad {
let aad_len = aad.len();
// Reason: CCM AAD 前缀 2 字节长度 + AAD 数据,补零至 16 字节对齐
let prefix_len = 2 + aad_len;
let padded_len = (prefix_len + 15) / 16 * 16;
let mut aad_buf = [0u8; 512]; // 足够大的栈缓冲区
if prefix_len <= aad_buf.len() {
aad_buf[0..2].copy_from_slice(&(aad_len as u16).to_be_bytes());
aad_buf[2..2 + aad_len].copy_from_slice(aad);
for chunk in aad_buf[..padded_len].chunks(16) {
let block: [u8; 16] = chunk.try_into().unwrap();
for i in 0..16 {
x[i] ^= block[i];
}
x = encrypt_block_raw(rk, &x);
}
}
}
for chunk in message.chunks(16) {
let mut block = [0u8; 16];
block[..chunk.len()].copy_from_slice(chunk);
for i in 0..16 {
x[i] ^= block[i];
}
x = encrypt_block_raw(rk, &x);
}
x
}
/// SM4-CCM 加密(AEAD
///
/// # 参数
/// - `nonce`: 12 字节
/// - `tag_len`: 认证标签长度,须为 4/6/8/10/12/14/16 之一
///
/// # 返回
/// 密文 || 认证标签(`tag_len` 字节)
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_ccm(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
plaintext: &[u8],
tag_len: usize,
) -> Vec<u8> {
assert!(
(4..=16).contains(&tag_len) && tag_len % 2 == 0,
"CCM tag_len 须为 4~16 的偶数"
);
let sm4 = Sm4Key::new(key);
let rk = sm4.round_keys();
let t = ccm_cbc_mac(rk, nonce, aad, plaintext, tag_len);
let mut a0 = [0u8; 16];
a0[0] = 2u8; // q-1 = 3-1 = 2
a0[1..13].copy_from_slice(nonce);
let s0 = encrypt_block_raw(rk, &a0);
let mut enc_tag = [0u8; 16];
for i in 0..tag_len {
enc_tag[i] = t[i] ^ s0[i];
}
let mut out = Vec::with_capacity(plaintext.len() + tag_len);
for (block_idx, chunk) in plaintext.chunks(16).enumerate() {
let mut a_i = a0;
let ctr_val = (block_idx as u32) + 1;
a_i[13] = (ctr_val >> 16) as u8;
a_i[14] = (ctr_val >> 8) as u8;
a_i[15] = ctr_val as u8;
let ks = encrypt_block_raw(rk, &a_i);
for (i, &b) in chunk.iter().enumerate() {
out.push(b ^ ks[i]);
}
}
out.extend_from_slice(&enc_tag[..tag_len]);
out
}
/// SM4-CCM 解密(AEAD
///
/// **先验证认证标签,验证通过后才解密。**
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_ccm(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
ciphertext_with_tag: &[u8],
tag_len: usize,
) -> Result<Vec<u8>, crate::error::Error> {
if ciphertext_with_tag.len() < tag_len {
return Err(crate::error::Error::InvalidInputLength);
}
let ct = &ciphertext_with_tag[..ciphertext_with_tag.len() - tag_len];
let received_tag = &ciphertext_with_tag[ciphertext_with_tag.len() - tag_len..];
let sm4 = Sm4Key::new(key);
let rk = sm4.round_keys();
let mut a0 = [0u8; 16];
a0[0] = 2u8;
a0[1..13].copy_from_slice(nonce);
let s0 = encrypt_block_raw(rk, &a0);
// Step 1: CTR 解密密文(得到候选明文)
let mut plaintext = Vec::with_capacity(ct.len());
for (block_idx, chunk) in ct.chunks(16).enumerate() {
let mut a_i = a0;
let ctr_val = (block_idx as u32) + 1;
a_i[13] = (ctr_val >> 16) as u8;
a_i[14] = (ctr_val >> 8) as u8;
a_i[15] = ctr_val as u8;
let ks = encrypt_block_raw(rk, &a_i);
for (i, &b) in chunk.iter().enumerate() {
plaintext.push(b ^ ks[i]);
}
}
// Step 2: 对候选明文重新计算 CBC-MAC
let t = ccm_cbc_mac(rk, nonce, aad, &plaintext, tag_len);
let mut expected_tag = [0u8; 16];
for i in 0..tag_len {
expected_tag[i] = t[i] ^ s0[i];
}
// Step 3: 常量时间比较,验证通过才返回明文
// Reason: 先验证后解密,防止选择密文攻击
if expected_tag[..tag_len].ct_eq(received_tag).unwrap_u8() == 0 {
return Err(crate::error::Error::AuthTagMismatch);
}
Ok(plaintext)
}
// ── XTS ──────────────────────────────────────────────────────────────────────
/// GF(2^128) 乘以 α(XTS tweak 更新)
fn xts_mul_alpha(tweak: &mut [u8; 16]) {
// Reason: XTS 使用反射位序的 GF(2^128),对应右移 + 0xE1 规约
let carry = tweak[15] & 1;
for i in (1..16).rev() {
tweak[i] = (tweak[i] >> 1) | ((tweak[i - 1] & 1) << 7);
}
tweak[0] >>= 1;
if carry == 1 {
tweak[0] ^= 0xE1;
}
}
/// SM4-XTS 加密(磁盘加密模式,GB/T 17964-2021
///
/// # 参数
/// - `key1`: 数据加密密钥(16 字节)
/// - `key2`: tweak 加密密钥(16 字节)
/// - `tweak_sector`: 扇区号(16 字节,通常为扇区编号的小端表示)
/// - `data`: 明文(须为 16 字节整倍数)
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_xts(
key1: &[u8; 16],
key2: &[u8; 16],
tweak_sector: &[u8; 16],
data: &[u8],
) -> Vec<u8> {
let sm4_1 = Sm4Key::new(key1);
let sm4_2 = Sm4Key::new(key2);
let mut tweak = *tweak_sector;
sm4_2.encrypt_block(&mut tweak);
let mut out = Vec::with_capacity(data.len());
for chunk in data.chunks(16) {
if chunk.len() == 16 {
let mut block = [0u8; 16];
for i in 0..16 {
block[i] = chunk[i] ^ tweak[i];
}
sm4_1.encrypt_block(&mut block);
for i in 0..16 {
out.push(block[i] ^ tweak[i]);
}
xts_mul_alpha(&mut tweak);
}
}
out
}
/// SM4-XTS 解密(磁盘加密模式)
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_xts(
key1: &[u8; 16],
key2: &[u8; 16],
tweak_sector: &[u8; 16],
data: &[u8],
) -> Vec<u8> {
let sm4_1 = Sm4Key::new(key1);
let sm4_2 = Sm4Key::new(key2);
let mut tweak = *tweak_sector;
sm4_2.encrypt_block(&mut tweak);
let mut out = Vec::with_capacity(data.len());
for chunk in data.chunks(16) {
if chunk.len() == 16 {
let mut block = [0u8; 16];
for i in 0..16 {
block[i] = chunk[i] ^ tweak[i];
}
sm4_1.decrypt_block(&mut block);
for i in 0..16 {
out.push(block[i] ^ tweak[i]);
}
xts_mul_alpha(&mut tweak);
}
}
out
}
// ── 测试 ──────────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
/// GB/T 32907-2016 附录 BCBC 模式测试向量
#[test]
fn test_cbc_vector() {
let key = [
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54,
0x32, 0x10,
];
let iv = [
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 ct = sm4_encrypt_cbc(&key, &iv, &plain);
let pt = sm4_decrypt_cbc(&key, &iv, &ct);
assert_eq!(pt, plain, "CBC 往返解密失败");
}
/// GCM 加解密往返测试
#[test]
fn test_gcm_roundtrip() {
let key = [0u8; 16];
let nonce = [1u8; 12];
let aad = b"additional data";
let plain = b"hello gcm world!";
let (ct, tag) = sm4_encrypt_gcm(&key, &nonce, aad, plain);
let pt = sm4_decrypt_gcm(&key, &nonce, aad, &ct, &tag).unwrap();
assert_eq!(pt, plain, "GCM 往返解密失败");
}
/// GCM tag 篡改检测
#[test]
fn test_gcm_tag_tamper() {
let key = [0u8; 16];
let nonce = [0u8; 12];
let (ct, mut tag) = sm4_encrypt_gcm(&key, &nonce, b"", b"secret");
tag[0] ^= 1;
assert!(
sm4_decrypt_gcm(&key, &nonce, b"", &ct, &tag).is_err(),
"篡改 tag 后应返回错误"
);
}
/// CCM 加解密往返测试
#[test]
fn test_ccm_roundtrip() {
let key = [0u8; 16];
let nonce = [2u8; 12];
let aad = b"ccm aad";
let plain = b"ccm plaintext!!!";
let ct = sm4_encrypt_ccm(&key, &nonce, aad, plain, 16);
let pt = sm4_decrypt_ccm(&key, &nonce, aad, &ct, 16).unwrap();
assert_eq!(pt, plain, "CCM 往返解密失败");
}
/// CCM tag 篡改检测(先验证后解密原则验证)
#[test]
fn test_ccm_tag_tamper() {
let key = [0u8; 16];
let nonce = [0u8; 12];
let mut ct = sm4_encrypt_ccm(&key, &nonce, b"", b"secret data here", 16);
// 篡改 tag(最后 16 字节)
let last = ct.len() - 1;
ct[last] ^= 1;
assert!(
sm4_decrypt_ccm(&key, &nonce, b"", &ct, 16).is_err(),
"篡改 CCM tag 后应返回错误"
);
}
/// OFB 自反性验证
#[test]
fn test_ofb_self_inverse() {
let key = [0xABu8; 16];
let iv = [0x12u8; 16];
let plain = b"ofb test message";
let ct = sm4_crypt_ofb(&key, &iv, plain);
let pt = sm4_crypt_ofb(&key, &iv, &ct);
assert_eq!(pt, plain, "OFB 应为自反模式");
}
}