mirror of
https://github.com/jixunmoe/tc_tea_rust
synced 2026-03-08 04:29:49 +00:00
refactor: try to minimize memory footprint.
This commit is contained in:
11
Cargo.toml
11
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"]
|
||||
|
||||
276
src/cbc.rs
Normal file
276
src/cbc.rs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
73
src/ecb.rs
Normal file
73
src/ecb.rs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
79
src/lib.rs
79
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<T: AsRef<[u8]>>(plaintext: T, key: &[u8]) -> Result<Vec<u8>, 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<T: AsRef<[u8]>>(encrypted: T, key: &[u8]) -> Result<Vec<u8>, 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))
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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<Box<[u8]>> {
|
||||
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<Box<[u8]>> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<T: AsRef<[u8]>, K: AsRef<[u8]>>(plaintext: T, key: K) -> Option<Box<[u8]>> {
|
||||
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<T: AsRef<[u8]>, K: AsRef<[u8]>>(encrypted: T, key: K) -> Option<Box<[u8]>> {
|
||||
tc_tea_cbc::decrypt(encrypted.as_ref(), key.as_ref())
|
||||
}
|
||||
Reference in New Issue
Block a user