发布 v0.2.0:新增 BLS 签名与 FPE 格式保留加密
新增功能: - BLS 签名:基于 BN256 配对的最小签名尺寸变体 - BLS 门限签名:Shamir 秘密分享 + Lagrange 插值聚合 - Hash-to-Curve:RFC 9380 兼容,SM3 消息扩展 - FPE 格式保留加密:基于 SM4 的 Feistel 密码 - SM9 Fp 平方根:Tonelli-Shanks 算法 文档更新: - README 添加 BLS/FPE 算法描述 - CHANGELOG 添加 v0.2.0 变更记录 - SECURITY 更新版本支持表
This commit is contained in:
+293
@@ -0,0 +1,293 @@
|
||||
//! FPE(Format-Preserving Encryption)保留格式加密
|
||||
//!
|
||||
//! 基于 FNR(Flexible Naor-Reingold)算法,使用 SM4 作为底层密码:
|
||||
//! - 支持 1~128 位任意长度的明密文域
|
||||
//! - 明密文在同一域内(位数相同)
|
||||
//! - 支持 tweak(调整值)参数化加密
|
||||
//!
|
||||
//! # 使用示例
|
||||
//!
|
||||
//! ```rust
|
||||
//! # #[cfg(feature = "alloc")]
|
||||
//! # {
|
||||
//! use libsmx::fpe::FpeKey;
|
||||
//!
|
||||
//! let key = [0u8; 16];
|
||||
//! let fpe = FpeKey::new(&key, 32).unwrap(); // 32 位域(如 IPv4 地址)
|
||||
//! let tweak = fpe.expand_tweak(b"my-tweak");
|
||||
//!
|
||||
//! let plaintext: u32 = 192_168_1_100; // 某 IPv4 地址
|
||||
//! let mut data = plaintext.to_be_bytes();
|
||||
//! let mut block = [0u8; 16];
|
||||
//! block[..4].copy_from_slice(&data);
|
||||
//!
|
||||
//! fpe.encrypt(&tweak, &mut block);
|
||||
//! fpe.decrypt(&tweak, &mut block);
|
||||
//!
|
||||
//! assert_eq!(&block[..4], &data);
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
mod fnr;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::sm4::Sm4Key;
|
||||
use fnr::{clear_high_bits, fnr_decrypt, fnr_encrypt};
|
||||
use zeroize::ZeroizeOnDrop;
|
||||
|
||||
/// FPE 扩展 tweak(15 字节)
|
||||
///
|
||||
/// 由 `FpeKey::expand_tweak` 从任意长度的 tweak 字节生成。
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct FpeTweak([u8; 15]);
|
||||
|
||||
/// FPE 密钥(SM4 密钥 + 位数配置)
|
||||
///
|
||||
/// 使用 ZeroizeOnDrop 确保密钥在 Drop 时自动清零。
|
||||
#[derive(ZeroizeOnDrop)]
|
||||
pub struct FpeKey {
|
||||
/// 底层 SM4 密钥
|
||||
key: Sm4Key,
|
||||
/// 有效位数(1~128)
|
||||
num_bits: usize,
|
||||
}
|
||||
|
||||
impl FpeKey {
|
||||
/// 创建 FPE 密钥
|
||||
///
|
||||
/// # 参数
|
||||
/// - `key`:16 字节 SM4 密钥
|
||||
/// - `num_bits`:明密文域的位数(1~128)
|
||||
///
|
||||
/// # 错误
|
||||
/// - `Error::InvalidInputLength`:`num_bits` 不在 1~128 范围内
|
||||
pub fn new(key: &[u8; 16], num_bits: usize) -> Result<Self, Error> {
|
||||
if num_bits == 0 || num_bits > 128 {
|
||||
return Err(Error::InvalidInputLength);
|
||||
}
|
||||
Ok(FpeKey {
|
||||
key: Sm4Key::new(key),
|
||||
num_bits,
|
||||
})
|
||||
}
|
||||
|
||||
/// 将任意长度的 tweak 扩展为 15 字节内部 tweak
|
||||
///
|
||||
/// 使用 SM4 对 tweak 进行哈希(CBC-MAC 风格)得到固定长度输出。
|
||||
pub fn expand_tweak(&self, tweak: &[u8]) -> FpeTweak {
|
||||
// 用 SM4 对 tweak 进行"哈希":
|
||||
// 将 tweak 分块,每块 XOR 进状态后 SM4 加密
|
||||
let mut state = [0u8; 16];
|
||||
// 存储 num_bits 到 state 前 2 字节(域参数绑定)
|
||||
state[0] = (self.num_bits >> 8) as u8;
|
||||
state[1] = self.num_bits as u8;
|
||||
|
||||
for chunk in tweak.chunks(16) {
|
||||
let mut block = state;
|
||||
for (i, &b) in chunk.iter().enumerate() {
|
||||
block[i] ^= b;
|
||||
}
|
||||
self.key.encrypt_block(&mut block);
|
||||
state = block;
|
||||
}
|
||||
|
||||
// 最终加密(确保即使 tweak 为空也有输出)
|
||||
self.key.encrypt_block(&mut state);
|
||||
|
||||
let mut out = [0u8; 15];
|
||||
out.copy_from_slice(&state[..15]);
|
||||
FpeTweak(out)
|
||||
}
|
||||
|
||||
/// 就地加密(前 num_bits 位)
|
||||
///
|
||||
/// `data` 的前 `num_bits` 位被加密,高于 `num_bits` 的位保持不变。
|
||||
///
|
||||
/// # 注意
|
||||
/// `data` 的位顺序:字节 0 的最高位是位 0(高位优先)。
|
||||
pub fn encrypt(&self, tweak: &FpeTweak, data: &mut [u8; 16]) {
|
||||
// 保存高于 num_bits 的位(不应被修改)
|
||||
let saved = save_high_bits(data, self.num_bits);
|
||||
clear_high_bits(data, self.num_bits);
|
||||
fnr_encrypt(&self.key, &tweak.0, data, self.num_bits);
|
||||
restore_high_bits(data, &saved, self.num_bits);
|
||||
}
|
||||
|
||||
/// 就地解密(前 num_bits 位)
|
||||
pub fn decrypt(&self, tweak: &FpeTweak, data: &mut [u8; 16]) {
|
||||
let saved = save_high_bits(data, self.num_bits);
|
||||
clear_high_bits(data, self.num_bits);
|
||||
fnr_decrypt(&self.key, &tweak.0, data, self.num_bits);
|
||||
restore_high_bits(data, &saved, self.num_bits);
|
||||
}
|
||||
|
||||
/// 返回有效位数
|
||||
pub fn num_bits(&self) -> usize {
|
||||
self.num_bits
|
||||
}
|
||||
}
|
||||
|
||||
/// 保存 data 中高于 n 位的位(用于还原)
|
||||
fn save_high_bits(data: &[u8; 16], n: usize) -> [u8; 16] {
|
||||
let mut saved = [0u8; 16];
|
||||
let full_bytes = n / 8;
|
||||
let rem = n % 8;
|
||||
if rem != 0 && full_bytes < 16 {
|
||||
// 保存 full_bytes 字节的高位部分(低 (8-rem) 位)
|
||||
let mask = 0xFF_u8 >> rem;
|
||||
saved[full_bytes] = data[full_bytes] & mask;
|
||||
}
|
||||
let start = full_bytes + if rem > 0 { 1 } else { 0 };
|
||||
saved[start..16].copy_from_slice(&data[start..16]);
|
||||
saved
|
||||
}
|
||||
|
||||
/// 将保存的高位还原到 data
|
||||
fn restore_high_bits(data: &mut [u8; 16], saved: &[u8; 16], n: usize) {
|
||||
let full_bytes = n / 8;
|
||||
let rem = n % 8;
|
||||
if rem != 0 && full_bytes < 16 {
|
||||
let mask = 0xFF_u8 >> rem; // 低 (8-rem) 位
|
||||
data[full_bytes] = (data[full_bytes] & !mask) | (saved[full_bytes] & mask);
|
||||
}
|
||||
let start = full_bytes + if rem > 0 { 1 } else { 0 };
|
||||
data[start..16].copy_from_slice(&saved[start..16]);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_fpe_new_valid() {
|
||||
assert!(FpeKey::new(&[0u8; 16], 1).is_ok());
|
||||
assert!(FpeKey::new(&[0u8; 16], 32).is_ok());
|
||||
assert!(FpeKey::new(&[0u8; 16], 128).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fpe_new_invalid() {
|
||||
assert!(FpeKey::new(&[0u8; 16], 0).is_err());
|
||||
assert!(FpeKey::new(&[0u8; 16], 129).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fpe_encrypt_decrypt_roundtrip_32bits() {
|
||||
let key = [0x01u8; 16];
|
||||
let fpe = FpeKey::new(&key, 32).unwrap();
|
||||
let tweak = fpe.expand_tweak(b"test-tweak");
|
||||
|
||||
// 明文:u32 = 12345678
|
||||
let mut data = [0u8; 16];
|
||||
data[..4].copy_from_slice(&12345678u32.to_be_bytes());
|
||||
let original = data;
|
||||
|
||||
fpe.encrypt(&tweak, &mut data);
|
||||
// 加密后应与原始不同
|
||||
assert_ne!(&data[..4], &original[..4], "加密后数据应变化");
|
||||
// 解密后应恢复原始
|
||||
fpe.decrypt(&tweak, &mut data);
|
||||
assert_eq!(&data[..4], &original[..4], "解密后应恢复原始明文");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fpe_encrypt_decrypt_roundtrip_8bits() {
|
||||
let key = [0xABu8; 16];
|
||||
let fpe = FpeKey::new(&key, 8).unwrap();
|
||||
let tweak = fpe.expand_tweak(b"tweak");
|
||||
|
||||
for val in 0u8..=255 {
|
||||
let mut data = [0u8; 16];
|
||||
data[0] = val;
|
||||
let original = data;
|
||||
|
||||
fpe.encrypt(&tweak, &mut data);
|
||||
fpe.decrypt(&tweak, &mut data);
|
||||
assert_eq!(data[0], original[0], "8位加解密往返应还原 val={}", val);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fpe_encrypt_decrypt_roundtrip_1bit() {
|
||||
let key = [0x99u8; 16];
|
||||
let fpe = FpeKey::new(&key, 1).unwrap();
|
||||
let tweak = fpe.expand_tweak(b"");
|
||||
|
||||
// 测试 0 和 1
|
||||
for val in [0u8, 0x80u8] {
|
||||
let mut data = [0u8; 16];
|
||||
data[0] = val;
|
||||
let original = data;
|
||||
fpe.encrypt(&tweak, &mut data);
|
||||
fpe.decrypt(&tweak, &mut data);
|
||||
assert_eq!(data[0] & 0x80, original[0] & 0x80, "1位加解密往返应还原");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fpe_encrypt_decrypt_roundtrip_128bits() {
|
||||
let key = [0x55u8; 16];
|
||||
let fpe = FpeKey::new(&key, 128).unwrap();
|
||||
let tweak = fpe.expand_tweak(b"full block");
|
||||
|
||||
let mut data = [0u8; 16];
|
||||
for (i, d) in data.iter_mut().enumerate() {
|
||||
*d = i as u8 * 17;
|
||||
}
|
||||
let original = data;
|
||||
|
||||
fpe.encrypt(&tweak, &mut data);
|
||||
fpe.decrypt(&tweak, &mut data);
|
||||
assert_eq!(data, original, "128位加解密往返应还原");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fpe_different_tweaks_different_output() {
|
||||
let key = [0x42u8; 16];
|
||||
let fpe = FpeKey::new(&key, 32).unwrap();
|
||||
let tweak1 = fpe.expand_tweak(b"tweak1");
|
||||
let tweak2 = fpe.expand_tweak(b"tweak2");
|
||||
|
||||
let mut d1 = [0u8; 16];
|
||||
let mut d2 = [0u8; 16];
|
||||
d1[0] = 0xDE;
|
||||
d1[1] = 0xAD;
|
||||
d1[2] = 0xBE;
|
||||
d1[3] = 0xEF;
|
||||
d2[..4].copy_from_slice(&d1[..4]);
|
||||
|
||||
fpe.encrypt(&tweak1, &mut d1);
|
||||
fpe.encrypt(&tweak2, &mut d2);
|
||||
assert_ne!(&d1[..4], &d2[..4], "不同 tweak 应产生不同密文");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fpe_high_bits_preserved() {
|
||||
// 验证高于 num_bits 的位在加密后不变
|
||||
let key = [0x11u8; 16];
|
||||
let fpe = FpeKey::new(&key, 4).unwrap(); // 只用高 4 位
|
||||
let tweak = fpe.expand_tweak(b"t");
|
||||
|
||||
let mut data = [0u8; 16];
|
||||
// 高 4 位为 0b1010,低 4 位为 0b0101
|
||||
data[0] = 0b1010_0101;
|
||||
// 字节 1~15 也有数据
|
||||
for (i, d) in data[1..].iter_mut().enumerate() {
|
||||
*d = (i + 1) as u8;
|
||||
}
|
||||
let saved_low = data[0] & 0x0F;
|
||||
let saved_rest: [u8; 15] = data[1..].try_into().unwrap();
|
||||
|
||||
fpe.encrypt(&tweak, &mut data);
|
||||
|
||||
// 低 4 位和字节 1~15 应保持不变
|
||||
assert_eq!(data[0] & 0x0F, saved_low, "低4位应不变");
|
||||
assert_eq!(&data[1..], &saved_rest, "字节1~15应不变");
|
||||
|
||||
// 解密后高 4 位应恢复
|
||||
let encrypted_high = data[0] & 0xF0;
|
||||
fpe.decrypt(&tweak, &mut data);
|
||||
assert_eq!(data[0] & 0xF0, 0b1010_0000, "解密后高4位应恢复");
|
||||
let _ = encrypted_high;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user