From 6614bd38702d2ca539287b475a6b51b076dc5bfa Mon Sep 17 00:00:00 2001 From: Jixun Wu <8041017-jixunmoe@users.noreply.gitlab.com> Date: Wed, 11 Sep 2024 01:36:30 +0100 Subject: [PATCH] refactor: try to minimize memory footprint. --- Cargo.toml | 11 +- src/cbc.rs | 276 +++++++++++++++++++++++++++++++++++++++++ src/ecb.rs | 73 +++++++++++ src/lib.rs | 79 +++++++++++- src/stream_ext.rs | 80 ------------ src/tc_tea_cbc.rs | 170 ------------------------- src/tc_tea_internal.rs | 63 ---------- src/tc_tea_public.rs | 33 ----- 8 files changed, 430 insertions(+), 355 deletions(-) create mode 100644 src/cbc.rs create mode 100644 src/ecb.rs delete mode 100644 src/stream_ext.rs delete mode 100644 src/tc_tea_cbc.rs delete mode 100644 src/tc_tea_internal.rs delete mode 100644 src/tc_tea_public.rs diff --git a/Cargo.toml b/Cargo.toml index ea90e6f..5f8f612 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,12 @@ categories = ["cryptography"] maintenance = { status = "as-is" } [dependencies] -rand = { version = "0.8.0", features = ["rand_chacha"] } -rand_chacha = "0.3.1" +thiserror = "1.0.63" +rand = { version = "0.8.5" } +rand_chacha = { version = "0.3.1", optional = true } +rand_pcg = { version = "0.3.1", optional = true } +byteorder = "1.5.0" [features] -default = ["secure_random"] -secure_random = ["rand/getrandom"] +default = ["rand_pcg"] +secure_random = ["rand/getrandom", "rand/rand_chacha", "rand_chacha"] diff --git a/src/cbc.rs b/src/cbc.rs new file mode 100644 index 0000000..9acd0f2 --- /dev/null +++ b/src/cbc.rs @@ -0,0 +1,276 @@ +use rand::prelude::*; +use std::cmp::min; + +use super::{ecb, TcTeaError}; + +const SALT_LEN: usize = 2; +const ZERO_LEN: usize = 7; +const FIXED_PADDING_LEN: usize = 1 + SALT_LEN + ZERO_LEN; + +/// Calculate expected size of encrypted data. +/// +/// `body_size` is the size of data you'd like to encrypt. +pub fn get_encrypted_size(body_size: usize) -> usize { + let len = FIXED_PADDING_LEN + body_size; + let pad_len = (8 - (len & 0b0111)) & 0b0111; + len + pad_len +} + +fn xor_tea_block(a: &[u8; 8], b: &[u8; 8]) -> [u8; 8] { + let mut dest = *a; + dest.iter_mut().zip(b).for_each(|(a, b)| *a ^= *b); + dest +} + +#[inline(always)] +fn encrypt_round( + cipher: &mut [u8], + plain: &[u8], + key: &[u32; 4], + iv1: &mut [u8; 8], + iv2: &mut [u8; 8], +) { + let mut plain_block = [0u8; 8]; + plain_block.copy_from_slice(plain); + + let iv2_next = xor_tea_block(&plain_block, iv1); + let mut result = iv2_next; + ecb::encrypt(&mut result, key); + + let cipher_block = xor_tea_block(&result, iv2); + *iv1 = cipher_block; + *iv2 = iv2_next; + cipher[..8].copy_from_slice(&cipher_block); +} + +pub fn encrypt<'a>( + cipher: &'a mut [u8], + plain: &[u8], + key: &[u32; 4], +) -> Result<&'a [u8], TcTeaError> { + // buffer size calculation + let len = FIXED_PADDING_LEN + plain.len(); + let pad_len = (8 - (len & 0b0111)) & 0b0111; + let expected_output_len = len + pad_len; // add our padding + if cipher.len() < expected_output_len { + Err(TcTeaError::DecryptBufferTooSmall( + expected_output_len, + cipher.len(), + ))?; + } + + let header_len = 1 + pad_len + SALT_LEN; + + // Setup buffer + let cipher = &mut cipher[..expected_output_len]; + let mut header = [0u8; 16]; + + // Set up a header with random padding/salt + #[cfg(feature = "secure_random")] + ChaCha20Rng::from_entropy().fill_bytes(&mut header[0..header_len]); + #[cfg(not(feature = "secure_random"))] + rand_pcg::Pcg32::from_entropy().fill_bytes(&mut header[0..header_len]); + + // Build header + let copy_to_header_len = min(16 - header_len, plain.len()); + let (plain_header, plain) = plain.split_at(copy_to_header_len); + + header[0] = (header[0] & 0b1111_1000) | ((pad_len as u8) & 0b0000_0111); + header[header_len..header_len + copy_to_header_len].copy_from_slice(plain_header); + + { + let mut iv1 = [0u8; 8]; + let mut iv2 = [0u8; 8]; + + let plain_last_block_len = plain.len() % 8; + let (plain, plain_last_block) = plain.split_at(plain.len() - plain_last_block_len); + + // Encrypt first 2 blocks from the header, then whole blocks + // cbc_encrypt_round(cipher, &header, key, &mut iv1, &mut iv2); + encrypt_round(cipher, &header[..8], key, &mut iv1, &mut iv2); + let cipher = &mut cipher[8..]; + + encrypt_round(cipher, &header[8..], key, &mut iv1, &mut iv2); + let mut cipher = &mut cipher[8..]; + + if !plain.is_empty() { + for (plain, cipher) in plain.chunks_exact(8).zip(cipher.chunks_exact_mut(8)) { + encrypt_round(cipher, plain, key, &mut iv1, &mut iv2); + } + cipher = &mut cipher[plain.len()..]; + } + + if plain_last_block_len != 0 { + let mut last_block = [0u8; 8]; + last_block[..plain_last_block_len].copy_from_slice(plain_last_block); + encrypt_round(cipher, &last_block, key, &mut iv1, &mut iv2); + } + } + + // Done. + Ok(cipher) +} + +#[inline(always)] +fn decrypt_round( + plain: &mut [u8], + cipher: &[u8], + key: &[u32; 4], + iv1: &mut [u8; 8], + iv2: &mut [u8; 8], +) { + let mut cipher_block = [0u8; 8]; + cipher_block.copy_from_slice(cipher); + + let mut result = xor_tea_block(&cipher_block, iv2); + ecb::decrypt(&mut result, key); + let plain_block = xor_tea_block(&result, iv1); + + *iv1 = cipher_block; + *iv2 = result; + + plain[..8].copy_from_slice(&plain_block); +} + +pub fn decrypt<'a>( + plain: &'a mut [u8], + cipher: &[u8], + key: &[u32; 4], +) -> Result<&'a [u8], TcTeaError> { + let input_len = cipher.len(); + if (input_len < FIXED_PADDING_LEN) || (input_len % 8 != 0) { + Err(TcTeaError::InvalidDataSize(input_len))?; + } + let output_len = plain.len(); + if output_len < input_len { + Err(TcTeaError::DecryptBufferTooSmall(input_len, output_len))?; + } + + let plain = &mut plain[..input_len]; + let mut iv1 = [0u8; 8]; + let mut iv2 = [0u8; 8]; + for (cipher, plain) in cipher.chunks_exact(8).zip(plain.chunks_exact_mut(8)) { + decrypt_round(plain, cipher, key, &mut iv1, &mut iv2); + } + + let pad_size = usize::from(plain[0] & 0b111); + + // Prefixed with "pad_size", "padding", "salt" + let start_loc = 1 + pad_size + SALT_LEN; + let end_loc = input_len - ZERO_LEN; + + if plain[end_loc..].iter().fold(0u8, |acc, v| acc | v) != 0 { + plain.fill(0); + Err(TcTeaError::InvalidPadding)? + } + + Ok(&plain[start_loc..end_loc]) +} + +#[cfg(test)] +mod tests { + use super::*; + + // Known good data, generated from its C++ implementation + const GOOD_ENCRYPTED_DATA: [u8; 24] = [ + 0x91, 0x09, 0x51, 0x62, 0xe3, 0xf5, 0xb6, 0xdc, // + 0x6b, 0x41, 0x4b, 0x50, 0xd1, 0xa5, 0xb8, 0x4e, // + 0xc5, 0x0d, 0x0c, 0x1b, 0x11, 0x96, 0xfd, 0x3c, // + ]; + + const ENCRYPTION_KEY: [u32; 4] = [0x31323334, 0x35363738, 0x41424344, 0x45464748]; + + const EXPECTED_PLAIN_TEXT: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; + + #[test] + fn tc_tea_basic_decryption() -> Result<(), TcTeaError> { + let mut plain = vec![0u8; 24]; + let result = decrypt(&mut plain, &GOOD_ENCRYPTED_DATA, &ENCRYPTION_KEY)?; + assert_eq!(result, &EXPECTED_PLAIN_TEXT); + Ok(()) + } + + #[test] + fn tc_tea_decryption_reject_non_zero_byte() { + let mut bad_data = GOOD_ENCRYPTED_DATA; + bad_data[23] ^= 0xff; // last byte + let mut plain = vec![0xffu8; 24]; + assert_eq!( + decrypt(&mut plain, &bad_data, &ENCRYPTION_KEY), + Err(TcTeaError::InvalidPadding) + ); + } + + #[test] + fn tc_tea_encrypt_empty() -> Result<(), TcTeaError> { + let mut cipher_buffer = [0xffu8; 100]; + let cipher = encrypt(&mut cipher_buffer, b"", &ENCRYPTION_KEY)?; + assert_eq!(cipher.len(), 16); + + let mut plain = vec![0xffu8; 24]; + // Since encryption utilises random numbers, we are just going to + let decrypted = decrypt(&mut plain, cipher, &ENCRYPTION_KEY)?; + assert_eq!(decrypted, b""); + + Ok(()) + } + + #[test] + fn tc_tea_basic_encryption() -> Result<(), TcTeaError> { + let mut cipher_buffer = [0xffu8; 100]; + let cipher = encrypt(&mut cipher_buffer, &EXPECTED_PLAIN_TEXT, &ENCRYPTION_KEY)?; + assert_eq!(cipher.len(), 24); + + let mut plain = vec![0xffu8; 24]; + // Since encryption utilises random numbers, we are just going to + let decrypted = decrypt(&mut plain, cipher, &ENCRYPTION_KEY)?; + assert_eq!(decrypted, &EXPECTED_PLAIN_TEXT); + + Ok(()) + } + + #[test] + fn tc_tea_test_long_encryption() -> Result<(), TcTeaError> { + let mut cipher_buffer = [0xffu8; 100]; + let input = b"...test data by Jixun ... ... test hello aaa"; + for _ in 0..16 { + let cipher = encrypt(&mut cipher_buffer, input, &ENCRYPTION_KEY)?; + assert_eq!(cipher.len() % 8, 0); + assert!(cipher.len() > input.len()); + + // Since encryption utilises random numbers, we are just going to + let mut plain = vec![0xffu8; cipher.len()]; + let decrypted = decrypt(&mut plain, cipher, &ENCRYPTION_KEY)?; + assert_eq!(decrypted, input); + } + + Ok(()) + } + + #[test] + fn tc_tea_test_various_len() -> Result<(), TcTeaError> { + let mut cipher_buffer = [0xffu8; 100]; + let mut plain_buffer = [0xffu8; 100]; + + let input = b"...test data by Jixun ... ... test hello aaa"; + for test_len in 0usize..input.len() { + let input = &input[..test_len]; + let cipher = encrypt(&mut cipher_buffer, input, &ENCRYPTION_KEY)?; + let decrypted = decrypt(&mut plain_buffer, cipher, &ENCRYPTION_KEY)?; + assert_eq!(decrypted, input); + } + + Ok(()) + } + + #[test] + fn test_calc_encrypted_size() { + assert_eq!(get_encrypted_size(0), 16); + assert_eq!(get_encrypted_size(1), 16); + assert_eq!(get_encrypted_size(6), 16); + + assert_eq!(get_encrypted_size(7), 24); + assert_eq!(get_encrypted_size(14), 24); + assert_eq!(get_encrypted_size(15), 32); + } +} diff --git a/src/ecb.rs b/src/ecb.rs new file mode 100644 index 0000000..8f0cd16 --- /dev/null +++ b/src/ecb.rs @@ -0,0 +1,73 @@ +use byteorder::{ByteOrder, BE}; + +// Tencent chooses 16 rounds instead of traditional 32 rounds. +const ROUNDS: u32 = 16; +const DELTA: u32 = 0x9e3779b9; + +/// Perform a single round of encrypting/decrypting wrapping arithmetics +fn ecb_single_round(value: u32, sum: u32, key1: u32, key2: u32) -> u32 { + let left = value.wrapping_shl(4).wrapping_add(key1); + let right = value.wrapping_shr(5).wrapping_add(key2); + let mid = sum.wrapping_add(value); + + left ^ mid ^ right +} + +/// Perform a 16 round TEA ECB encryption. +pub fn encrypt(block: &mut [u8; 8], k: &[u32; 4]) { + let mut y = BE::read_u32(&block[..4]); + let mut z = BE::read_u32(&block[4..]); + let mut sum = 0_u32; + + for _ in 0..ROUNDS { + sum = sum.wrapping_add(DELTA); + + y = y.wrapping_add(ecb_single_round(z, sum, k[0], k[1])); + z = z.wrapping_add(ecb_single_round(y, sum, k[2], k[3])); + } + + BE::write_u32(&mut block[..4], y); + BE::write_u32(&mut block[4..], z); +} + +/// Perform a 16 round TEA ECB decryption. +pub fn decrypt(block: &mut [u8; 8], key: &[u32; 4]) { + let mut y = BE::read_u32(&block[..4]); + let mut z = BE::read_u32(&block[4..]); + let mut sum = DELTA.wrapping_mul(ROUNDS); + + for _ in 0..ROUNDS { + z = z.wrapping_sub(ecb_single_round(y, sum, key[2], key[3])); + y = y.wrapping_sub(ecb_single_round(z, sum, key[0], key[1])); + + sum = sum.wrapping_sub(DELTA); + } + + BE::write_u32(&mut block[..4], y); + BE::write_u32(&mut block[4..], z); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_decryption() { + let mut data: [u8; 8] = [0x56, 0x27, 0x6b, 0xa9, 0x80, 0xb9, 0xec, 0x16]; + let key: [u32; 4] = [0x01020304, 0x05060708, 0x090a0b0c, 0x0d0e0f00]; + let expected: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; + + decrypt(&mut data, &key); + assert_eq!(data, expected); + } + + #[test] + fn test_encryption() { + let mut data: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; + let key: [u32; 4] = [0x01020304, 0x05060708, 0x090a0b0c, 0x0d0e0f00]; + let expected: [u8; 8] = [0x56, 0x27, 0x6b, 0xa9, 0x80, 0xb9, 0xec, 0x16]; + + encrypt(&mut data, &key); + assert_eq!(data, expected); + } +} diff --git a/src/lib.rs b/src/lib.rs index 01f2e4d..e25d27f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,9 +2,78 @@ //! //! Notably, it uses a different round number and uses a "tweaked" CBC mode. -mod stream_ext; -mod tc_tea_public; -mod tc_tea_internal; -mod tc_tea_cbc; +use byteorder::{ByteOrder, BE}; +use thiserror::Error; -pub use tc_tea_public::*; +pub mod cbc; +pub mod ecb; + +#[derive(Error, Debug, PartialEq)] +pub enum TcTeaError { + #[error("Key size mismatch. Required 16 bytes, got {0} bytes")] + KeyTooShort(usize), + #[error("Cipher text size invalid. {0} mod 8 != 0.")] + InvalidDataSize(usize), + #[error("Decrypt buffer size too small, it should be at least {0} bytes (actual={1} bytes).")] + DecryptBufferTooSmall(usize, usize), + #[error("Encrypt buffer size too small, it should be at least {0} bytes (actual={1} bytes).")] + EncryptBufferTooSmall(usize, usize), + #[error("Invalid data padding")] + InvalidPadding, + #[error("Slice error.")] + SliceError, +} + +/// Parse key to u32 array +pub fn parse_key(key: &[u8]) -> Result<[u32; 4], TcTeaError> { + let key_chunks = match key.len() { + 16 => key.chunks(4), + key_length => Err(TcTeaError::KeyTooShort(key_length))?, + }; + + let mut parsed = [0u32; 4]; + for (key, key_chunk) in parsed.iter_mut().zip(key_chunks) { + *key = BE::read_u32(key_chunk); + } + Ok(parsed) +} + +/// Encrypts an arbitrary length sized data in the following way: +/// +/// * PadLen (1 byte) +/// * Padding (variable, 0-7byte) +/// * Salt (2 bytes) +/// * Body (? bytes) +/// * Zero (7 bytes) +/// +/// Returned bytes will always have a length multiple of 8. +/// +/// PadLen/Padding/Salt are randomly bytes, with a minimum of 21 bits (3 * 8 - 3) randomness. +/// +/// # Panics +/// +/// If random number generator fails, it will panic. +pub fn encrypt>(plaintext: T, key: &[u8]) -> Result, TcTeaError> { + let key = parse_key(key)?; + let plaintext = plaintext.as_ref(); + let mut cipher = vec![0u8; plaintext.len()]; + let result = cbc::decrypt(&mut cipher, plaintext, &key)?; + Ok(Vec::from(result)) +} + +/// Decrypts a byte array containing the following: +/// +/// * PadLen (1 byte) +/// * Padding (variable, 0-7byte) +/// * Salt (2 bytes) +/// * Body (? bytes) +/// * Zero (7 bytes) +/// +/// PadLen is taken from the last 3 bit of the first byte. +pub fn decrypt>(encrypted: T, key: &[u8]) -> Result, TcTeaError> { + let key = parse_key(key)?; + let encrypted = encrypted.as_ref(); + let mut plain = vec![0u8; encrypted.len()]; + let result = cbc::decrypt(&mut plain, encrypted, &key)?; + Ok(Vec::from(result)) +} diff --git a/src/stream_ext.rs b/src/stream_ext.rs deleted file mode 100644 index 6b71ef1..0000000 --- a/src/stream_ext.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::ops::BitOr; - -pub trait StreamExt { - fn read_u32_be(&self, offset: usize) -> u32; - fn write_u32_be(&mut self, offset: usize, value: u32); - fn xor_block(&mut self, dst_offset: usize, size: usize, src: &[u8], src_offset: usize); - fn is_all_zeros(&self) -> bool; - - fn xor_prev_tea_block(&mut self, offset: usize); - fn copy_tea_block(&mut self, offset: usize, src: &[u8], src_offset: usize); - fn xor_tea_block(&mut self, dst_offset: usize, src: &[u8], src_offset: usize); -} - -impl StreamExt for [u8] { - #[inline] - fn read_u32_be(&self, offset: usize) -> u32 { - (u32::from(self[offset]) << 24) - | (u32::from(self[offset + 1]) << 16) - | (u32::from(self[offset + 2]) << 8) - | (u32::from(self[offset + 3])) - } - - #[inline] - fn write_u32_be(&mut self, offset: usize, value: u32) { - self[offset..offset + 4].copy_from_slice(&value.to_be_bytes()); - } - - #[inline] - fn xor_block(&mut self, dst_offset: usize, size: usize, src: &[u8], src_offset: usize) { - for i in 0..size { - self[dst_offset + i] ^= src[src_offset + i]; - } - } - - /// Constant time all zero comparison - /// Attempts to do constant time comparison, - /// but probably gets optimised away by llvm... lol - fn is_all_zeros(&self) -> bool { - self.iter().fold(0u8, |acc, b| acc.bitor(b)) == 0 - } - - #[inline] - fn xor_prev_tea_block(&mut self, offset: usize) { - for i in offset..offset + 8 { - self[i] ^= self[i - 8]; - } - } - - #[inline] - fn copy_tea_block(&mut self, offset: usize, src: &[u8], src_offset: usize) { - self[offset..offset + 8] - .as_mut() - .copy_from_slice(&src[src_offset..src_offset + 8]); - } - - #[inline] - fn xor_tea_block(&mut self, dst_offset: usize, src: &[u8], src_offset: usize) { - self.xor_block(dst_offset, 8, src, src_offset); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_read_u32_be() { - let v1 = [1, 2, 3, 4]; - let v2 = [0x7f, 0xff, 0xee, 0xdd, 0xcc]; - assert_eq!(v1.read_u32_be(0), 0x01020304); - assert_eq!(v2.read_u32_be(1), 0xffeeddcc); - } - - #[test] - fn test_write_u32_be() { - let v2 = &mut [0x7fu8, 0xff, 0xee, 0xdd, 0xcc]; - v2.write_u32_be(0, 0x01020304); - assert_eq!(v2, &[1u8, 2, 3, 4, 0xcc]); - } -} diff --git a/src/tc_tea_cbc.rs b/src/tc_tea_cbc.rs deleted file mode 100644 index a434e8b..0000000 --- a/src/tc_tea_cbc.rs +++ /dev/null @@ -1,170 +0,0 @@ -use rand::prelude::*; -use rand_chacha::ChaCha20Rng; - -use super::stream_ext::StreamExt; -use super::tc_tea_internal::{ecb_decrypt, ecb_encrypt, parse_key}; - -const SALT_LEN: usize = 2; -const ZERO_LEN: usize = 7; -const FIXED_PADDING_LEN: usize = 1 + SALT_LEN + ZERO_LEN; - -/// Calculate expected size of encrypted data. -/// -/// `body_size` is the size of data you'd like to encrypt. -pub fn calc_encrypted_size(body_size: usize) -> usize { - let len = FIXED_PADDING_LEN + body_size; - let pad_len = (8 - (len & 0b0111)) & 0b0111; - len + pad_len -} - -pub fn encrypt(plaintext: &[u8], key: &[u8]) -> Option> { - let key = parse_key(key)?; - - // buffer size calculation - let len = FIXED_PADDING_LEN + plaintext.len(); - let pad_len = (8 - (len & 0b0111)) & 0b0111; - let len = len + pad_len; // add our padding - debug_assert_eq!( - len, - calc_encrypted_size(plaintext.len()), - "encrypted size calculation mismatch" - ); - let header_len = 1 + pad_len + SALT_LEN; - - // Setup buffer - let mut encrypted = vec![0u8; len].into_boxed_slice(); - let mut iv1 = vec![0u8; len].into_boxed_slice(); - - // Setup a header with random padding/salt - #[cfg(feature = "secure_random")] - ChaCha20Rng::from_entropy().fill_bytes(&mut encrypted[0..header_len]); - - #[cfg(not(feature = "secure_random"))] - ChaCha20Rng::from_rng(thread_rng()) - .unwrap() - .fill_bytes(&mut encrypted[0..header_len]); - - encrypted[0] = (encrypted[0] & 0b1111_1000) | ((pad_len as u8) & 0b0000_0111); - - // Copy input to destination buffer. - encrypted[header_len..header_len + plaintext.len()] - .as_mut() - .copy_from_slice(plaintext); - - // First block - iv1.copy_tea_block(0, &encrypted, 0); // preserve iv2 for first block - ecb_encrypt(&mut encrypted[0..8], &key); // transform first block - - // Rest of the block - for i in (8..len).step_by(8) { - encrypted.xor_prev_tea_block(i); // XOR iv2 - iv1.copy_tea_block(i, &encrypted, i); // store iv1 - ecb_encrypt(&mut encrypted[i..i + 8], &key); // TEA ECB - encrypted.xor_tea_block(i, &iv1, i - 8); // XOR iv1 (from prev block) - } - - // Done. - Some(encrypted) -} - -pub fn decrypt(encrypted: &[u8], key: &[u8]) -> Option> { - let key = parse_key(key)?; - let len = encrypted.len(); - if (len < FIXED_PADDING_LEN) || (len % 8 != 0) { - return None; - } - - let mut decrypted_buf = encrypted.to_vec(); - - // First block - ecb_decrypt(&mut decrypted_buf[0..8], &key); - - // Rest of the block - for i in (8..len).step_by(8) { - decrypted_buf.xor_prev_tea_block(i); // xor iv1 - ecb_decrypt(&mut decrypted_buf[i..i + 8], &key); - } - - // Finalise: XOR iv2 (cipher text) - decrypted_buf.xor_block(8, len - 8, encrypted, 0); - - let pad_size = usize::from(decrypted_buf[0] & 0b111); - - // Prefixed with "pad_size", "padding", "salt" - let start_loc = 1 + pad_size + SALT_LEN; - let end_loc = len - ZERO_LEN; - - if decrypted_buf[end_loc..].is_all_zeros() { - Some( - decrypted_buf[start_loc..end_loc] - .to_vec() - .into_boxed_slice(), - ) - } else { - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - - // Known good data, generated from its C++ implementation - const GOOD_ENCRYPTED_DATA: [u8; 24] = [ - 0x91, 0x09, 0x51, 0x62, 0xe3, 0xf5, 0xb6, 0xdc, // - 0x6b, 0x41, 0x4b, 0x50, 0xd1, 0xa5, 0xb8, 0x4e, // - 0xc5, 0x0d, 0x0c, 0x1b, 0x11, 0x96, 0xfd, 0x3c, // - ]; - - const ENCRYPTION_KEY: &[u8; 16] = b"12345678ABCDEFGH"; - - const GOOD_DECRYPTED_DATA: [u8; 8] = [1u8, 2, 3, 4, 5, 6, 7, 8]; - - #[test] - fn tc_tea_basic_decryption() { - let result = decrypt(&GOOD_ENCRYPTED_DATA, ENCRYPTION_KEY).unwrap(); - assert_eq!(result, GOOD_DECRYPTED_DATA.into()); - } - - #[test] - fn tc_tea_decryption_reject_non_zero_byte() { - let mut bad_data = GOOD_ENCRYPTED_DATA; - bad_data[23] ^= 0xff; // last byte - assert!(decrypt(&bad_data, ENCRYPTION_KEY).is_none()); - } - - #[test] - fn tc_tea_basic_encryption() { - let encrypted = encrypt(&GOOD_DECRYPTED_DATA, ENCRYPTION_KEY).unwrap(); - assert_eq!(encrypted.len(), 24); - - // Since encryption utilises random numbers, we are just going to - let decrypted = decrypt(&encrypted, ENCRYPTION_KEY).unwrap(); - assert_eq!(decrypted, GOOD_DECRYPTED_DATA.into()); - } - - #[test] - fn tc_tea_test_long_encryption() { - let input = b"...test data by Jixun"; - for _ in 0..16 { - let encrypted = encrypt(input, ENCRYPTION_KEY).unwrap(); - assert_eq!(encrypted.len() % 8, 0); - assert!(encrypted.len() > input.len()); - - // Since encryption utilises random numbers, we are just going to - let decrypted = decrypt(&encrypted, ENCRYPTION_KEY).unwrap(); - assert_eq!(&*decrypted, input); - } - } - - #[test] - fn test_calc_encrypted_size() { - assert_eq!(calc_encrypted_size(0), 16); - assert_eq!(calc_encrypted_size(1), 16); - assert_eq!(calc_encrypted_size(6), 16); - - assert_eq!(calc_encrypted_size(7), 24); - assert_eq!(calc_encrypted_size(14), 24); - assert_eq!(calc_encrypted_size(15), 32); - } -} diff --git a/src/tc_tea_internal.rs b/src/tc_tea_internal.rs deleted file mode 100644 index f3fd475..0000000 --- a/src/tc_tea_internal.rs +++ /dev/null @@ -1,63 +0,0 @@ -use super::stream_ext::StreamExt; - -const ROUNDS: u32 = 16; -const DELTA: u32 = 0x9e3779b9; - -#[inline] -pub fn parse_key(key: &[u8]) -> Option<[u32; 4]> { - if key.len() < 16 { - return None; - } - - let mut k = [0u32; 4]; - for (i, k) in k.iter_mut().enumerate() { - *k = key.read_u32_be(i * 4); - } - Some(k) -} - -#[inline] -/// Perform a single round of encrypting/decrypting wrapping arithmetics -fn tc_tea_single_round_arithmetic(value: u32, sum: u32, key1: u32, key2: u32) -> u32 { - // ((y << 4) + k[2]) ^ (y + sum) ^ ((y >> 5) + k[3]); - - value.wrapping_shl(4).wrapping_add(key1) - ^ sum.wrapping_add(value) - ^ value.wrapping_shr(5).wrapping_add(key2) -} - -#[inline] -/// Perform a single operation of TEA ECB decryption. -pub fn ecb_decrypt(block: &mut [u8], k: &[u32; 4]) { - let mut y = block.read_u32_be(0); - let mut z = block.read_u32_be(4); - let mut sum = DELTA.wrapping_mul(ROUNDS); - - for _ in 0..ROUNDS { - z = z.wrapping_sub(tc_tea_single_round_arithmetic(y, sum, k[2], k[3])); - y = y.wrapping_sub(tc_tea_single_round_arithmetic(z, sum, k[0], k[1])); - - sum = sum.wrapping_sub(DELTA); - } - - block.write_u32_be(0, y); - block.write_u32_be(4, z); -} - -#[inline] -/// Perform a single operation of TEA ECB encryption. -pub fn ecb_encrypt(block: &mut [u8], k: &[u32; 4]) { - let mut y = block.read_u32_be(0); - let mut z = block.read_u32_be(4); - let mut sum = 0_u32; - - for _ in 0..ROUNDS { - sum = sum.wrapping_add(DELTA); - - y = y.wrapping_add(tc_tea_single_round_arithmetic(z, sum, k[0], k[1])); - z = z.wrapping_add(tc_tea_single_round_arithmetic(y, sum, k[2], k[3])); - } - - block.write_u32_be(0, y); - block.write_u32_be(4, z); -} diff --git a/src/tc_tea_public.rs b/src/tc_tea_public.rs deleted file mode 100644 index 345351d..0000000 --- a/src/tc_tea_public.rs +++ /dev/null @@ -1,33 +0,0 @@ -use super::tc_tea_cbc; - -/// Encrypts an arbitrary length sized data in the following way: -/// -/// * PadLen (1 byte) -/// * Padding (variable, 0-7byte) -/// * Salt (2 bytes) -/// * Body (? bytes) -/// * Zero (7 bytes) -/// -/// Returned bytes will always have a length multiple of 8. -/// -/// PadLen/Padding/Salt are randomly bytes, with a minimum of 21 bits (3 * 8 - 3) randomness. -/// -/// # Panics -/// -/// If random number generator fails, it will panic. -pub fn encrypt, K: AsRef<[u8]>>(plaintext: T, key: K) -> Option> { - tc_tea_cbc::encrypt(plaintext.as_ref(), key.as_ref()) -} - -/// Decrypts a byte array containing the following: -/// -/// * PadLen (1 byte) -/// * Padding (variable, 0-7byte) -/// * Salt (2 bytes) -/// * Body (? bytes) -/// * Zero (7 bytes) -/// -/// PadLen is taken from the last 3 bit of the first byte. -pub fn decrypt, K: AsRef<[u8]>>(encrypted: T, key: K) -> Option> { - tc_tea_cbc::decrypt(encrypted.as_ref(), key.as_ref()) -}