From 29ab1c22b2b12573e7dc5a6e8bdaa011e76f4716 Mon Sep 17 00:00:00 2001 From: Friedel Ziegelmayer Date: Tue, 13 Sep 2022 05:51:49 +0200 Subject: [PATCH] cfb-mode: add Buffer{Encryptor|Decryptor} (#17) --- cfb-mode/src/decrypt.rs | 129 ++++++++++++++++++++++++++++++++++++++- cfb-mode/src/encrypt.rs | 130 +++++++++++++++++++++++++++++++++++++++- cfb-mode/src/lib.rs | 4 +- cfb-mode/tests/aes.rs | 66 +++++++++++++++++++- 4 files changed, 323 insertions(+), 6 deletions(-) diff --git a/cfb-mode/src/decrypt.rs b/cfb-mode/src/decrypt.rs index b188a89..7e7c1d6 100644 --- a/cfb-mode/src/decrypt.rs +++ b/cfb-mode/src/decrypt.rs @@ -4,7 +4,7 @@ use cipher::{ inout::InOut, AlgorithmName, AsyncStreamCipher, Block, BlockBackend, BlockCipher, BlockClosure, BlockDecrypt, BlockDecryptMut, BlockEncryptMut, BlockSizeUser, InnerIvInit, Iv, IvState, ParBlocks, - ParBlocksSizeUser, + ParBlocksSizeUser, Unsigned, }; use core::fmt; @@ -21,6 +21,64 @@ where iv: Block, } +/// CFB mode buffered decryptor. +#[derive(Clone)] +pub struct BufDecryptor +where + C: BlockEncryptMut + BlockCipher, +{ + cipher: C, + iv: Block, + pos: usize, +} + +impl BufDecryptor +where + C: BlockEncryptMut + BlockCipher, +{ + /// Decrypt a buffer in multiple parts. + pub fn decrypt(&mut self, mut data: &mut [u8]) { + let bs = C::BlockSize::to_usize(); + let n = data.len(); + + if n < bs - self.pos { + xor_set2(data, &mut self.iv[self.pos..self.pos + n]); + self.pos += n; + return; + } + let (left, right) = { data }.split_at_mut(bs - self.pos); + data = right; + let mut iv = self.iv.clone(); + xor_set2(left, &mut iv[self.pos..]); + self.cipher.encrypt_block_mut(&mut iv); + + let mut chunks = data.chunks_exact_mut(bs); + for chunk in &mut chunks { + xor_set2(chunk, iv.as_mut_slice()); + self.cipher.encrypt_block_mut(&mut iv); + } + + let rem = chunks.into_remainder(); + xor_set2(rem, iv.as_mut_slice()); + self.pos = rem.len(); + self.iv = iv; + } + + /// Returns the current state (block and position) of the decryptor. + pub fn get_state(&self) -> (&Block, usize) { + (&self.iv, self.pos) + } + + /// Restore from the given state for resumption. + pub fn from_state(cipher: C, iv: &Block, pos: usize) -> Self { + Self { + cipher, + iv: iv.clone(), + pos, + } + } +} + impl BlockSizeUser for Decryptor where C: BlockEncryptMut + BlockCipher, @@ -47,6 +105,13 @@ where type Inner = C; } +impl InnerUser for BufDecryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type Inner = C; +} + impl IvSizeUser for Decryptor where C: BlockEncryptMut + BlockCipher, @@ -54,6 +119,13 @@ where type IvSize = C::BlockSize; } +impl IvSizeUser for BufDecryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type IvSize = C::BlockSize; +} + impl InnerIvInit for Decryptor where C: BlockEncryptMut + BlockCipher, @@ -66,6 +138,18 @@ where } } +impl InnerIvInit for BufDecryptor +where + C: BlockEncryptMut + BlockCipher, +{ + #[inline] + fn inner_iv_init(mut cipher: C, iv: &Iv) -> Self { + let mut iv = iv.clone(); + cipher.encrypt_block_mut(&mut iv); + Self { cipher, iv, pos: 0 } + } +} + impl IvState for Decryptor where C: BlockEncryptMut + BlockDecrypt + BlockCipher, @@ -89,6 +173,17 @@ where } } +impl AlgorithmName for BufDecryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("cfb::BufDecryptor<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + impl fmt::Debug for Decryptor where C: BlockEncryptMut + BlockCipher + AlgorithmName, @@ -100,6 +195,17 @@ where } } +impl fmt::Debug for BufDecryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("cfb::BufDecryptor<")?; + ::write_alg_name(f)?; + f.write_str("> { ... }") + } +} + #[cfg(feature = "zeroize")] #[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] impl Drop for Decryptor { @@ -108,10 +214,22 @@ impl Drop for Decryptor { } } +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Drop for BufDecryptor { + fn drop(&mut self) { + self.iv.zeroize(); + } +} + #[cfg(feature = "zeroize")] #[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] impl ZeroizeOnDrop for Decryptor {} +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for BufDecryptor {} + struct Closure<'a, BS, BC> where BS: ArrayLength, @@ -193,3 +311,12 @@ where *self.iv = t[n - 1].clone(); } } + +#[inline(always)] +fn xor_set2(buf1: &mut [u8], buf2: &mut [u8]) { + for (a, b) in buf1.iter_mut().zip(buf2) { + let t = *a; + *a ^= *b; + *b = t; + } +} diff --git a/cfb-mode/src/encrypt.rs b/cfb-mode/src/encrypt.rs index 42a7fd6..cde30b8 100644 --- a/cfb-mode/src/encrypt.rs +++ b/cfb-mode/src/encrypt.rs @@ -4,7 +4,7 @@ use cipher::{ generic_array::{ArrayLength, GenericArray}, inout::InOut, AlgorithmName, AsyncStreamCipher, Block, BlockBackend, BlockCipher, BlockClosure, BlockDecrypt, - BlockEncryptMut, BlockSizeUser, InnerIvInit, Iv, IvState, ParBlocksSizeUser, + BlockEncryptMut, BlockSizeUser, InnerIvInit, Iv, IvState, ParBlocksSizeUser, Unsigned, }; use core::fmt; @@ -21,6 +21,65 @@ where iv: Block, } +/// CFB mode buffered encryptor. +#[derive(Clone)] +pub struct BufEncryptor +where + C: BlockEncryptMut + BlockCipher, +{ + cipher: C, + iv: Block, + pos: usize, +} + +impl BufEncryptor +where + C: BlockEncryptMut + BlockCipher, +{ + /// Encrypt a buffer in multiple parts. + pub fn encrypt(&mut self, mut data: &mut [u8]) { + let bs = C::BlockSize::USIZE; + let n = data.len(); + + if n < bs - self.pos { + xor_set1(data, &mut self.iv[self.pos..self.pos + n]); + self.pos += n; + return; + } + + let (left, right) = { data }.split_at_mut(bs - self.pos); + data = right; + let mut iv = self.iv.clone(); + xor_set1(left, &mut iv[self.pos..]); + self.cipher.encrypt_block_mut(&mut iv); + + let mut chunks = data.chunks_exact_mut(bs); + for chunk in &mut chunks { + xor_set1(chunk, iv.as_mut_slice()); + self.cipher.encrypt_block_mut(&mut iv); + } + + let rem = chunks.into_remainder(); + xor_set1(rem, iv.as_mut_slice()); + self.pos = rem.len(); + self.iv = iv; + } + + /// Returns the current state (block and position) of the decryptor. + pub fn get_state(&self) -> (&Block, usize) { + (&self.iv, self.pos) + } + + /// Restore from the given state for resumption. + pub fn from_state(cipher: C, iv: &Block, pos: usize) -> Self { + Self { + cipher, + iv: iv.clone(), + pos, + } + } +} + impl BlockSizeUser for Encryptor where C: BlockEncryptMut + BlockCipher, @@ -47,6 +106,13 @@ where type Inner = C; } +impl InnerUser for BufEncryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type Inner = C; +} + impl IvSizeUser for Encryptor where C: BlockEncryptMut + BlockCipher, @@ -54,6 +120,13 @@ where type IvSize = C::BlockSize; } +impl IvSizeUser for BufEncryptor +where + C: BlockEncryptMut + BlockCipher, +{ + type IvSize = C::BlockSize; +} + impl InnerIvInit for Encryptor where C: BlockEncryptMut + BlockCipher, @@ -66,6 +139,18 @@ where } } +impl InnerIvInit for BufEncryptor +where + C: BlockEncryptMut + BlockCipher, +{ + #[inline] + fn inner_iv_init(mut cipher: C, iv: &Iv) -> Self { + let mut iv = iv.clone(); + cipher.encrypt_block_mut(&mut iv); + Self { cipher, iv, pos: 0 } + } +} + impl IvState for Encryptor where C: BlockEncryptMut + BlockDecrypt + BlockCipher, @@ -78,6 +163,17 @@ where } } +impl AlgorithmName for BufEncryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("cfb::BufEncryptor<")?; + ::write_alg_name(f)?; + f.write_str(">") + } +} + impl AlgorithmName for Encryptor where C: BlockEncryptMut + BlockCipher + AlgorithmName, @@ -100,6 +196,17 @@ where } } +impl fmt::Debug for BufEncryptor +where + C: BlockEncryptMut + BlockCipher + AlgorithmName, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("cfb::BufEncryptor<")?; + ::write_alg_name(f)?; + f.write_str("> { ... }") + } +} + #[cfg(feature = "zeroize")] #[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] impl Drop for Encryptor { @@ -108,10 +215,22 @@ impl Drop for Encryptor { } } +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl Drop for BufEncryptor { + fn drop(&mut self) { + self.iv.zeroize(); + } +} + #[cfg(feature = "zeroize")] #[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] impl ZeroizeOnDrop for Encryptor {} +#[cfg(feature = "zeroize")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] +impl ZeroizeOnDrop for BufEncryptor {} + struct Closure<'a, BS, BC> where BS: ArrayLength, @@ -179,3 +298,12 @@ where *self.iv = t; } } + +#[inline(always)] +fn xor_set1(buf1: &mut [u8], buf2: &mut [u8]) { + for (a, b) in buf1.iter_mut().zip(buf2) { + let t = *a ^ *b; + *a = t; + *b = t; + } +} diff --git a/cfb-mode/src/lib.rs b/cfb-mode/src/lib.rs index a98f40d..c33d0b7 100644 --- a/cfb-mode/src/lib.rs +++ b/cfb-mode/src/lib.rs @@ -64,5 +64,5 @@ mod decrypt; mod encrypt; pub use cipher; -pub use decrypt::Decryptor; -pub use encrypt::Encryptor; +pub use decrypt::{BufDecryptor, Decryptor}; +pub use encrypt::{BufEncryptor, Encryptor}; diff --git a/cfb-mode/tests/aes.rs b/cfb-mode/tests/aes.rs index 2f8bf98..188ea47 100644 --- a/cfb-mode/tests/aes.rs +++ b/cfb-mode/tests/aes.rs @@ -1,6 +1,6 @@ use aes::*; -use cfb_mode::{Decryptor, Encryptor}; -use cipher::{block_mode_dec_test, block_mode_enc_test, iv_state_test}; +use cfb_mode::{BufDecryptor, BufEncryptor, Decryptor, Encryptor}; +use cipher::{block_mode_dec_test, block_mode_enc_test, iv_state_test, KeyInit}; iv_state_test!(aes128_cfb_enc_iv_state, Encryptor, encrypt); iv_state_test!(aes128_cfb_dec_iv_state, Decryptor, decrypt); @@ -53,3 +53,65 @@ fn aes128_cfb_async_test() { assert_eq!(t, &pt[..i]); } } + +#[test] +fn aes128_cfb_buffered_test() { + use cipher::{AsyncStreamCipher, KeyIvInit}; + + type Enc = Encryptor; + + type BufEnc = BufEncryptor; + type BufDec = BufDecryptor; + + let key = [42; 16]; + let iv = [24; 16]; + let mut pt = [0u8; 101]; + for (i, b) in pt.iter_mut().enumerate() { + *b = (i % 11) as u8; + } + + // unbuffered + let enc = Enc::new_from_slices(&key, &iv).unwrap(); + let mut ct = pt.clone(); + enc.encrypt(&mut ct); + + // buffered + for i in 1..100 { + let mut buf_enc = BufEnc::new_from_slices(&key, &iv).unwrap(); + let mut ct2 = pt.clone(); + for chunk in ct2.chunks_mut(i) { + buf_enc.encrypt(chunk); + } + assert_eq!(ct2, ct); + + let mut buf_dec = BufDec::new_from_slices(&key, &iv).unwrap(); + for chunk in ct2.chunks_mut(i) { + buf_dec.decrypt(chunk); + } + assert_eq!(ct2, pt); + } + + // buffered with restore + for i in 1..100 { + let mut buf_enc = BufEnc::new_from_slices(&key, &iv).unwrap(); + let mut ct2 = pt.clone(); + for chunk in ct2.chunks_mut(i) { + let (iv, pos) = buf_enc.get_state(); + let cipher = Aes128::new_from_slice(&key).unwrap(); + buf_enc = BufEnc::from_state(cipher, iv, pos); + + buf_enc.encrypt(chunk); + } + assert_eq!(ct2, ct); + + let mut buf_dec = BufDec::new_from_slices(&key, &iv).unwrap(); + for chunk in ct2.chunks_mut(i) { + let (iv, pos) = buf_dec.get_state(); + let cipher = Aes128::new_from_slice(&key).unwrap(); + buf_dec = BufDec::from_state(cipher, iv, pos); + + buf_dec.decrypt(chunk); + } + assert_eq!(ct2, pt); + } +}