use crate::cipher::pkcs7::Pkcs7Padding;
use crate::cipher::provider::SymmetricCipher;
use crate::error::{CryptoError, Result};
use crate::i18n::translate;
use crate::key::Key;
use crate::random::SecureRandom;
use crate::side_channel::{
protect_critical_operation, RotatingSboxMasking, SideChannelConfig, SideChannelContext,
};
use crate::types::Algorithm;
use std::sync::{Arc, Mutex};
pub struct Sm4GcmProvider {
side_channel_context: Option<Arc<Mutex<SideChannelContext>>>,
_rotating_sbox: Option<Arc<Mutex<RotatingSboxMasking>>>,
}
impl Sm4GcmProvider {
pub fn new() -> Self {
let side_channel_context = Some(Arc::new(Mutex::new(SideChannelContext::new(
SideChannelConfig::default(),
))));
let rotating_sbox = RotatingSboxMasking::new(4)
.ok()
.map(|sbox| Arc::new(Mutex::new(sbox)));
Self {
side_channel_context,
_rotating_sbox: rotating_sbox,
}
}
#[allow(dead_code)]
pub fn with_side_channel_config(config: SideChannelConfig) -> Self {
let side_channel_context = Some(Arc::new(Mutex::new(SideChannelContext::new(config))));
let rotating_sbox = RotatingSboxMasking::new(4)
.ok()
.map(|sbox| Arc::new(Mutex::new(sbox)));
Self {
side_channel_context,
_rotating_sbox: rotating_sbox,
}
}
}
impl Default for Sm4GcmProvider {
fn default() -> Self {
Self::new()
}
}
impl Sm4GcmProvider {
fn encrypt_internal(
&self,
key: &Key,
plaintext: &[u8],
nonce: &[u8],
aad: Option<&[u8]>,
use_padding: bool,
) -> Result<Vec<u8>> {
use ghash::{
universal_hash::{KeyInit, UniversalHash},
GHash,
};
use sm4::cipher::{KeyIvInit, StreamCipher};
use sm4::Sm4;
let secret = key.secret_bytes()?;
let key_bytes: [u8; 16] = secret.as_bytes().try_into().map_err(|_| {
CryptoError::KeyError("Invalid SM4 key length, must be 128 bits".into())
})?;
let data_to_encrypt = if use_padding {
Pkcs7Padding::pad(plaintext, 16)?
} else {
plaintext.to_vec()
};
let mut ghash = GHash::new(&key_bytes.into());
match aad {
Some(a) if !a.is_empty() => {
ghash.update_padded(a);
}
_ => {}
}
let mut iv = [0u8; 16];
iv[..12].copy_from_slice(nonce);
iv[15] = 2;
let mut ciphertext = data_to_encrypt;
type Sm4Ctr = ctr::Ctr128BE<Sm4>;
let mut cipher = Sm4Ctr::new(&key_bytes.into(), &iv.into());
cipher.apply_keystream(&mut ciphertext);
ghash.update_padded(&ciphertext);
let mut len_block = [0u8; 16];
let aad_len = aad.map(|a| a.len() as u64).unwrap_or(0) * 8;
let ct_len = (ciphertext.len() as u64) * 8;
len_block[..8].copy_from_slice(&aad_len.to_be_bytes());
len_block[8..].copy_from_slice(&ct_len.to_be_bytes());
ghash.update_padded(&len_block);
let mut tag = ghash.finalize();
let mut j0 = [0u8; 16];
j0[..12].copy_from_slice(nonce);
j0[15] = 1;
let mut tag_mask = [0u8; 16];
let mut mask_cipher = Sm4Ctr::new(&key_bytes.into(), &j0.into());
mask_cipher.apply_keystream(&mut tag_mask);
for i in 0..16 {
tag[i] ^= tag_mask[i];
}
let mut result = ciphertext;
result.extend_from_slice(&tag);
Ok(result)
}
fn decrypt_internal(
&self,
key: &Key,
ciphertext_with_tag: &[u8],
nonce: &[u8],
aad: Option<&[u8]>,
use_padding: bool,
) -> Result<Vec<u8>> {
use ghash::{
universal_hash::{KeyInit, UniversalHash},
GHash,
};
use sm4::cipher::{KeyIvInit, StreamCipher};
use sm4::Sm4;
if ciphertext_with_tag.len() < 16 {
return Err(CryptoError::DecryptionFailed(
"Ciphertext too short for tag".into(),
));
}
let (ciphertext, received_tag) =
ciphertext_with_tag.split_at(ciphertext_with_tag.len() - 16);
let secret = key.secret_bytes()?;
let key_bytes: [u8; 16] = secret
.as_bytes()
.try_into()
.map_err(|_| CryptoError::KeyError("无效的 SM4 密钥长度,必须是 128 位".into()))?;
let mut ghash = GHash::new(&key_bytes.into());
if let Some(a) = aad {
if !a.is_empty() {
ghash.update_padded(a);
}
}
ghash.update_padded(ciphertext);
let mut len_block = [0u8; 16];
let aad_len = aad.map(|a| a.len() as u64).unwrap_or(0) * 8;
let ct_len = (ciphertext.len() as u64) * 8;
len_block[..8].copy_from_slice(&aad_len.to_be_bytes());
len_block[8..].copy_from_slice(&ct_len.to_be_bytes());
ghash.update_padded(&len_block);
let mut tag = ghash.finalize();
let mut j0 = [0u8; 16];
j0[..12].copy_from_slice(nonce);
j0[15] = 1;
let mut tag_mask = [0u8; 16];
type Sm4Ctr = ctr::Ctr128BE<Sm4>;
let mut mask_cipher = Sm4Ctr::new(&key_bytes.into(), &j0.into());
mask_cipher.apply_keystream(&mut tag_mask);
for i in 0..16 {
tag[i] ^= tag_mask[i];
}
use subtle::ConstantTimeEq;
if tag.as_slice().ct_eq(received_tag).unwrap_u8() != 1 {
return Err(CryptoError::DecryptionFailed("标签不匹配".into()));
}
let mut iv = [0u8; 16];
iv[..12].copy_from_slice(nonce);
iv[15] = 2;
let mut plaintext = ciphertext.to_vec();
let mut cipher = Sm4Ctr::new(&key_bytes.into(), &iv.into());
cipher.apply_keystream(&mut plaintext);
if use_padding {
Pkcs7Padding::unpad(&plaintext, 16)
} else {
Ok(plaintext)
}
}
}
impl SymmetricCipher for Sm4GcmProvider {
fn encrypt(&self, key: &Key, plaintext: &[u8], aad: Option<&[u8]>) -> Result<Vec<u8>> {
if key.algorithm() != Algorithm::SM4GCM {
return Err(CryptoError::UnsupportedAlgorithm(
"Key algo mismatch".into(),
));
}
if crate::fips::FipsContext::is_enabled() {
return Err(CryptoError::FipsError(translate(
"error.sm4_not_allowed_in_fips_mode",
)));
}
let mut nonce = [0u8; 12];
SecureRandom::new()?.fill(&mut nonce)?;
let ciphertext = if let Some(context) = &self.side_channel_context {
let mut context_guard = context.lock().unwrap();
protect_critical_operation(&mut context_guard, || {
self.encrypt_internal(key, plaintext, &nonce, aad, true)
})?
} else {
self.encrypt_internal(key, plaintext, &nonce, aad, true)?
};
let mut result = nonce.to_vec();
result.extend_from_slice(&ciphertext);
Ok(result)
}
fn decrypt(&self, key: &Key, ciphertext: &[u8], aad: Option<&[u8]>) -> Result<Vec<u8>> {
if key.algorithm() != Algorithm::SM4GCM {
return Err(CryptoError::UnsupportedAlgorithm(
"Key algo mismatch".into(),
));
}
if crate::fips::FipsContext::is_enabled() {
return Err(CryptoError::FipsError(
"SM4 not allowed in FIPS mode".into(),
));
}
if ciphertext.len() < 12 + 16 {
return Err(CryptoError::DecryptionFailed(translate(
"error.invalid_length",
)));
}
let (nonce, data) = ciphertext.split_at(12);
if let Some(context) = &self.side_channel_context {
let mut context_guard = context.lock().unwrap();
protect_critical_operation(&mut context_guard, || {
self.decrypt_internal(key, data, nonce, aad, true)
})
} else {
self.decrypt_internal(key, data, nonce, aad, true)
}
}
fn algorithm(&self) -> Algorithm {
Algorithm::SM4GCM
}
fn encrypt_with_nonce(
&self,
key: &Key,
plaintext: &[u8],
nonce: &[u8],
aad: Option<&[u8]>,
) -> Result<Vec<u8>> {
if key.algorithm() != Algorithm::SM4GCM {
return Err(CryptoError::UnsupportedAlgorithm(
"Key algo mismatch".into(),
));
}
if nonce.len() != 12 {
return Err(CryptoError::EncryptionFailed("Invalid nonce length".into()));
}
if let Some(context) = &self.side_channel_context {
let mut context_guard = context.lock().unwrap();
protect_critical_operation(&mut context_guard, || {
self.encrypt_internal(key, plaintext, nonce, aad, false)
})
} else {
self.encrypt_internal(key, plaintext, nonce, aad, false)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sm4_pkcs7_padding() {
let provider = Sm4GcmProvider::new();
let key_data = vec![0x01; 16];
let key = Key::new_active(Algorithm::SM4GCM, key_data).unwrap();
let plaintext = b"Hello SM4!!";
let ciphertext = provider.encrypt(&key, plaintext, None).unwrap();
let decrypted = provider.decrypt(&key, &ciphertext, None).unwrap();
assert_eq!(decrypted, plaintext);
let plaintext_16 = b"1234567890123456";
let ciphertext_16 = provider.encrypt(&key, plaintext_16, None).unwrap();
let decrypted_16 = provider.decrypt(&key, &ciphertext_16, None).unwrap();
assert_eq!(decrypted_16, plaintext_16);
assert_eq!(ciphertext_16.len(), 12 + 32 + 16);
}
#[test]
fn test_sm4_with_side_channel_protection() {
let provider = Sm4GcmProvider::new();
let key_data = vec![0x01; 16]; let mut key = Key::new(Algorithm::SM4GCM, key_data).unwrap();
key.activate(None).unwrap();
let plaintext = b"Hello, SM4 with side-channel protection!";
let ciphertext = provider.encrypt(&key, plaintext, None).unwrap();
assert!(!ciphertext.is_empty());
assert_ne!(ciphertext, plaintext);
let decrypted = provider.decrypt(&key, &ciphertext, None).unwrap();
assert_eq!(decrypted, plaintext);
println!("SM4 encryption/decryption with side-channel protection completed successfully");
}
#[test]
fn test_sm4_fips_rejection() {
let provider = Sm4GcmProvider::new();
let key_data = vec![0x01; 16];
let key = Key::new_active(Algorithm::SM4GCM, key_data).unwrap();
let plaintext = b"Test data";
let result = provider.encrypt(&key, plaintext, None);
assert!(result.is_ok());
}
}