From e7c311eacb54cb0669a37e8048c5e6e8e89362b6 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Mon, 4 Sep 2023 00:10:23 +0800 Subject: [PATCH] feat: v0.1.0 --- Cargo.toml | 2 +- README.md | 21 +++++- examples/bench.rs | 6 +- examples/encrypt.rs | 12 ++++ src/decryptor.rs | 157 ++++++++++++++++++++++++++++++++++++++++++++ src/encryptor.rs | 17 ++--- src/lib.rs | 15 ++++- src/util.rs | 17 +++++ 8 files changed, 230 insertions(+), 17 deletions(-) create mode 100644 examples/encrypt.rs diff --git a/Cargo.toml b/Cargo.toml index 3cc175c..6297af4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sm4-gcm" -version = "0.0.0" +version = "0.1.0" edition = "2021" authors = ["Hatter Jiang"] repository = "https://git.hatter.ink/hatter/sm4-gcm" diff --git a/README.md b/README.md index d050914..b444f48 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,21 @@ # sm4-gcm -PENGING... +Encrypt & Decrypt test code: +```rust +fn main() { + let key = Sm4Key([0u8; 16]); + let nonce = [0u8; 12]; + let plaintext = b"Hello World!"; + + let ciphertext = sm4_gcm::sm4_gcm_encrypt(&key, &nonce, plaintext); + println!("Encrypted: {}", hex::encode(&ciphertext)); + let decrypted = sm4_gcm::sm4_gcm_decrypt(&key, &nonce, &ciphertext).unwrap(); + println!("Decrypted: {}", String::from_utf8_lossy(&decrypted)); +} +``` -BC test code: +Generate test vector BC test code: ```java public static void encryptGcmNoPadding(String key, String data, String nonce, String associatedData) throws Exception { Cipher cipher = Cipher.getInstance("SM4/GCM/NoPadding", BouncyCastleProvider.PROVIDER_NAME); @@ -20,3 +32,8 @@ public static void encryptGcmNoPadding(String key, String data, String nonce, St ``` +Benchmark @MacBook Pro (Retina, 15-inch, Late 2013/2 GHz Quad-Core Intel Core i7) +```text +$ cargo run --release --example bench +SM4/GCM encrypt : 65.69 M/s +``` diff --git a/examples/bench.rs b/examples/bench.rs index 21d65dd..ae13b5f 100644 --- a/examples/bench.rs +++ b/examples/bench.rs @@ -1,6 +1,6 @@ use benchmark_simple::{Bench, Options}; -use sm4_gcm::Sm4GcmStreamEncryptor; +use sm4_gcm::{Sm4GcmStreamEncryptor, Sm4Key}; fn main() { let bench = Bench::new(); @@ -21,9 +21,9 @@ fn main() { fn test_sm4_encrypt(m: &mut [u8]) { - let key = [0u8; 16]; + let key = Sm4Key([0u8; 16]); let nonce = [0u8; 12]; - let mut encryptor = Sm4GcmStreamEncryptor::new(key, &nonce); + let mut encryptor = Sm4GcmStreamEncryptor::new(&key, &nonce); encryptor.update(m); encryptor.finalize(); diff --git a/examples/encrypt.rs b/examples/encrypt.rs new file mode 100644 index 0000000..fcc8fe3 --- /dev/null +++ b/examples/encrypt.rs @@ -0,0 +1,12 @@ +use sm4_gcm::Sm4Key; + +fn main() { + let key = Sm4Key([0u8; 16]); + let nonce = [0u8; 12]; + let plaintext = b"Hello World!"; + + let ciphertext = sm4_gcm::sm4_gcm_encrypt(&key, &nonce, plaintext); + println!("Encrypted: {}", hex::encode(&ciphertext)); + let decrypted = sm4_gcm::sm4_gcm_decrypt(&key, &nonce, &ciphertext).unwrap(); + println!("Decrypted: {}", String::from_utf8_lossy(&decrypted)); +} \ No newline at end of file diff --git a/src/decryptor.rs b/src/decryptor.rs index e69de29..3e051bf 100644 --- a/src/decryptor.rs +++ b/src/decryptor.rs @@ -0,0 +1,157 @@ +use ghash::GHash; +use ghash::universal_hash::UniversalHash; +use sm4::cipher::{Block, BlockEncrypt, KeyInit}; +use sm4::cipher::generic_array::GenericArray; +use sm4::Sm4; +use zeroize::Zeroize; + +use crate::util::{BLOCK_SIZE, inc_32, msb_s, normalize_nonce, Sm4Block, Sm4Key, u8to128}; + +pub fn sm4_gcm_decrypt(key: &Sm4Key, nonce: &[u8], ciphertext: &[u8]) -> Result, String> { + sm4_gcm_aad_decrypt(key, nonce, &[], ciphertext) +} + +pub fn sm4_gcm_aad_decrypt(key: &Sm4Key, nonce: &[u8], aad: &[u8], ciphertext: &[u8]) -> Result, String> { + let mut decryptor = Sm4GcmStreamDecryptor::new(key, nonce); + if aad.len() > 0 { + decryptor.init_adata(aad); + } + let mut msg1 = decryptor.update(ciphertext); + let msg2 = decryptor.finalize()?; + msg1.extend_from_slice(&msg2); + Ok(msg1) +} + +pub struct Sm4GcmStreamDecryptor { + cipher: Sm4, + message_buffer: Vec, + ghash: GHash, + init_nonce: u128, + encryption_nonce: u128, + adata_len: usize, + message_len: usize, +} + +impl Sm4GcmStreamDecryptor { + pub fn new(key: &Sm4Key, nonce: &[u8]) -> Self { + let mut key = GenericArray::from(key.0); + let cipher = Sm4::new(&key); + + let mut ghash_key = ghash::Key::default(); + cipher.encrypt_block(&mut ghash_key); + let ghash = GHash::new(&ghash_key); + ghash_key.zeroize(); + key.zeroize(); + + let mut s = Self { + cipher, + message_buffer: vec![], + ghash, + init_nonce: 0, + encryption_nonce: 0, + adata_len: 0, + message_len: 0, + }; + let (_, normalized_nonce) = s.normalize_nonce(nonce); + s.init_nonce = normalized_nonce; + s.encryption_nonce = normalized_nonce; + s + } + + pub fn init_adata(&mut self, adata: &[u8]) { + if adata.len() > 0 { + self.adata_len += adata.len(); + self.ghash.update_padded(adata); + } + } + + pub fn update(&mut self, bytes: &[u8]) -> Vec { + self.message_buffer.extend_from_slice(bytes); + let message_buffer_slice = self.message_buffer.as_slice(); + let message_buffer_len = message_buffer_slice.len(); + if message_buffer_len < 32 { + return Vec::with_capacity(0); + } + let blocks_count = (message_buffer_len / 16) - 1; + + let mut blocks = Vec::with_capacity(blocks_count); + for _ in 0..blocks_count { + self.encryption_nonce = inc_32(self.encryption_nonce); + let ctr = self.encryption_nonce.to_be_bytes(); + blocks.push(Block::::clone_from_slice(&ctr)); + } + self.cipher.encrypt_blocks(&mut blocks); + + let encrypted_message = &message_buffer_slice[0..blocks_count * BLOCK_SIZE]; + self.ghash.update_padded(encrypted_message); + let mut plaintext_message = encrypted_message.to_vec(); + for i in 0..blocks_count { + let chunk = &mut plaintext_message[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE]; + let block = blocks[i].as_slice(); + for k in 0..BLOCK_SIZE { + chunk[k] ^= block[k]; + } + } + self.message_buffer = message_buffer_slice[blocks_count * BLOCK_SIZE..].to_vec(); + self.message_len += plaintext_message.len(); + + plaintext_message + } + + pub fn finalize(&mut self) -> Result, String> { + let mut plaintext_message = Vec::with_capacity(16); + let message_buffer_len = self.message_buffer.len(); + if message_buffer_len > 16 { + // last block and this block len is less than 128 bits + self.encryption_nonce = inc_32(self.encryption_nonce); + let mut ctr = self.encryption_nonce.to_be_bytes(); + let block = Block::::from_mut_slice(&mut ctr); + self.cipher.encrypt_block(block); + + let chunk = &self.message_buffer[0..message_buffer_len - 16]; + let msb = msb_s(chunk.len() * 8, block.as_slice()); + let y = u8to128(chunk) ^ u8to128(&msb); + plaintext_message.extend_from_slice(&y.to_be_bytes()[16 - chunk.len()..16]); + self.ghash.update_padded(&self.message_buffer[0..message_buffer_len - 16]); + self.message_len += plaintext_message.len(); + } + let adata_bit_len = self.adata_len * 8; + let message_bit_len = self.message_len * 8; + let mut adata_and_message_len = Vec::with_capacity(BLOCK_SIZE); + adata_and_message_len.extend_from_slice(&(adata_bit_len as u64).to_be_bytes()); + adata_and_message_len.extend_from_slice(&(message_bit_len as u64).to_be_bytes()); + self.ghash.update_padded(&adata_and_message_len); + + let tag = self.calculate_tag(); + let message_tag = &self.message_buffer[message_buffer_len - 16..]; + + if message_tag != tag.as_slice() { + Err(format!("Tag mismatch, expected: {:2x}, actual: {:2x}", + u8to128(&tag), u8to128(message_tag))) + } else { + Ok(plaintext_message) + } + } + + fn calculate_tag(&mut self) -> Vec { + let mut bs = self.init_nonce.to_be_bytes().clone(); + let block = Block::::from_mut_slice(&mut bs); + self.cipher.encrypt_block(block); + let ghash = self.ghash.clone().finalize(); + let tag_trunk = ghash.as_slice(); + let y = u8to128(&tag_trunk) ^ u8to128(&block.as_slice()); + y.to_be_bytes().to_vec() + } + + fn ghash_key(&mut self) -> u128 { + let mut block = [0u8; BLOCK_SIZE]; + let block = Block::::from_mut_slice(&mut block); + self.cipher.encrypt_block(block); + u8to128(&block.as_slice()) + } + + fn normalize_nonce(&mut self, nonce_bytes: &[u8]) -> (u128, u128) { + let ghash_key = self.ghash_key(); + normalize_nonce(ghash_key, nonce_bytes) + } +} \ No newline at end of file diff --git a/src/encryptor.rs b/src/encryptor.rs index 2e87d9f..6e47b1d 100644 --- a/src/encryptor.rs +++ b/src/encryptor.rs @@ -5,13 +5,13 @@ use sm4::cipher::generic_array::GenericArray; use sm4::Sm4; use zeroize::Zeroize; -use crate::util::{BLOCK_SIZE, inc_32, msb_s, normalize_nonce, Sm4Block, u8to128}; +use crate::util::{BLOCK_SIZE, inc_32, msb_s, normalize_nonce, Sm4Block, Sm4Key, u8to128}; -pub fn sm4_gcm_encrypt(key: [u8; 16], nonce: &[u8], message: &[u8]) -> Vec { +pub fn sm4_gcm_encrypt(key: &Sm4Key, nonce: &[u8], message: &[u8]) -> Vec { sm4_gcm_aad_encrypt(key, nonce, &[], message) } -pub fn sm4_gcm_aad_encrypt(key: [u8; 16], nonce: &[u8], aad: &[u8], message: &[u8]) -> Vec { +pub fn sm4_gcm_aad_encrypt(key: &Sm4Key, nonce: &[u8], aad: &[u8], message: &[u8]) -> Vec { let mut encryptor = Sm4GcmStreamEncryptor::new(key, nonce); if aad.len() > 0 { encryptor.init_adata(aad); @@ -34,17 +34,18 @@ pub struct Sm4GcmStreamEncryptor { } impl Sm4GcmStreamEncryptor { - pub fn new(key: [u8; 16], nonce: &[u8]) -> Self { - let key = GenericArray::from(key); - let aes = Sm4::new(&key); + pub fn new(key: &Sm4Key, nonce: &[u8]) -> Self { + let mut key = GenericArray::from(key.0); + let cipher = Sm4::new(&key); let mut ghash_key = ghash::Key::default(); - aes.encrypt_block(&mut ghash_key); + cipher.encrypt_block(&mut ghash_key); let ghash = GHash::new(&ghash_key); ghash_key.zeroize(); + key.zeroize(); let mut s = Self { - cipher: aes, + cipher, message_buffer: vec![], ghash, init_nonce: 0, diff --git a/src/lib.rs b/src/lib.rs index 5fe669e..aac62c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,11 @@ +pub use decryptor::Sm4GcmStreamDecryptor; pub use encryptor::Sm4GcmStreamEncryptor; +pub use crate::decryptor::sm4_gcm_aad_decrypt; +pub use crate::decryptor::sm4_gcm_decrypt; pub use crate::encryptor::sm4_gcm_aad_encrypt; pub use crate::encryptor::sm4_gcm_encrypt; +pub use crate::util::Sm4Key; mod util; mod encryptor; @@ -9,13 +13,14 @@ mod decryptor; // Test vectors are all from BC #[test] -fn test_encrypt() { +fn test_sm4_gcm() { let data = vec![ ([0u8; 16], [0u8; 12], &[][..], &b"A"[..], "3c0a0922976fa15e835bc96750e730d967"), ([0u8; 16], [0u8; 12], &[][..], &b"hello world"[..], "1587c6137e306fed6a6a5f49539b6dd6fe2b7872c3279636db07c2"), ([0u8; 16], [0xffu8; 12], &[][..], &b"Hello World!"[..], "cba3523bdf74096f3de1f9160a5adb7bf385dea4d50c910e663ec75a"), ([0xffu8; 16], [0xffu8; 12], &[][..], &b"Hello World!"[..], "99eb1206b5b2a9f9c7d7ec4a81de507f5d79938a10ccd91da68d2fb1"), ([0xffu8; 16], [0xffu8; 12], &[0xaau8, 0xbbu8, 0xccu8][..], &b"Hello World!"[..], "99eb1206b5b2a9f9c7d7ec4a7be091388b3049363189e64a47d20c19"), + ([0xffu8; 16], [0xffu8; 12], &[0u8, 1u8, 2u8, 3u8][..], &b"Hello World!"[..], "99eb1206b5b2a9f9c7d7ec4ac157a74de0381b3aa170385a113d4f31"), ([0u8; 16], [0u8; 12], &[][..], &b"Hello World!Hello World!Hello World!Hello World!Hello World!Hello World!"[..], "3587c6137e304fed6a6a5fc0f78e01e5ea4b604843929848601d4b1600e35c1\ @@ -24,7 +29,11 @@ fn test_encrypt() { ]; for (key, nonce, aad, message, expected) in data { - let encrypted = hex::encode(sm4_gcm_aad_encrypt(key, &nonce, aad, message)); - assert_eq!(expected, &encrypted); + let encrypted = sm4_gcm_aad_encrypt(&Sm4Key(key), &nonce, aad, message); + let encrypted_hex = hex::encode(&encrypted); + assert_eq!(expected, &encrypted_hex); + + let decrypted = sm4_gcm_aad_decrypt(&Sm4Key(key), &nonce, aad, &encrypted).unwrap(); + assert_eq!(message, decrypted.as_slice()); } } \ No newline at end of file diff --git a/src/util.rs b/src/util.rs index 4b176ee..78022c1 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,8 @@ +use std::error::Error; + use sm4::cipher::BlockSizeUser; use sm4::cipher::consts::U16; +use zeroize::Zeroize; pub(crate) struct Sm4Block {} @@ -9,6 +12,20 @@ impl BlockSizeUser for Sm4Block { pub(crate) const BLOCK_SIZE: usize = 16; +pub struct Sm4Key(pub [u8; 16]); + +impl Sm4Key { + pub fn from_slice(key: &[u8]) -> Result> { + Ok(Self(key.try_into()?)) + } +} + +impl Drop for Sm4Key { + fn drop(&mut self) { + self.0.zeroize(); + } +} + // R = 11100001 || 0(120) const R: u128 = 0b11100001 << 120;