diff --git a/Cargo.lock b/Cargo.lock index fced1e50..dae59ec5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,7 @@ dependencies = [ "aes-soft", "aesni 0.9.0", "ctr", - "stream-cipher", + "stream-cipher 0.8.0-pre", ] [[package]] @@ -50,7 +50,7 @@ checksum = "b6a4d655ae633a96d0acaf0fd7e76aafb8ca5732739bba37aac6f882c8fce656" dependencies = [ "block-cipher", "opaque-debug", - "stream-cipher", + "stream-cipher 0.7.1", ] [[package]] @@ -80,7 +80,7 @@ version = "0.5.0" dependencies = [ "aes", "hex-literal", - "stream-cipher", + "stream-cipher 0.7.1", ] [[package]] @@ -89,7 +89,7 @@ version = "0.5.0" dependencies = [ "aes", "hex-literal", - "stream-cipher", + "stream-cipher 0.7.1", ] [[package]] @@ -98,7 +98,7 @@ version = "0.5.0" dependencies = [ "hex-literal", "rand_core", - "stream-cipher", + "stream-cipher 0.7.1", "zeroize", ] @@ -107,7 +107,8 @@ name = "ctr" version = "0.5.0" dependencies = [ "aes", - "stream-cipher", + "hex-literal", + "stream-cipher 0.8.0-pre", ] [[package]] @@ -124,7 +125,7 @@ dependencies = [ name = "hc-256" version = "0.2.0" dependencies = [ - "stream-cipher", + "stream-cipher 0.7.1", "zeroize", ] @@ -153,7 +154,7 @@ version = "0.3.0" dependencies = [ "aes", "hex-literal", - "stream-cipher", + "stream-cipher 0.7.1", ] [[package]] @@ -178,7 +179,7 @@ checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" name = "salsa20" version = "0.6.0" dependencies = [ - "stream-cipher", + "stream-cipher 0.7.1", "zeroize", ] @@ -193,6 +194,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "stream-cipher" +version = "0.8.0-pre" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81c22786b7392254461cf5f358c143035828bf5d95850d4adc483faf065f5b9e" +dependencies = [ + "blobby", + "block-cipher", + "generic-array", +] + [[package]] name = "typenum" version = "1.12.0" diff --git a/aes-ctr/Cargo.toml b/aes-ctr/Cargo.toml index 407227c6..5804b86e 100644 --- a/aes-ctr/Cargo.toml +++ b/aes-ctr/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.md" edition = "2018" [dependencies] -stream-cipher = "0.7" +stream-cipher = "0.8.0-pre" [target.'cfg(not(all(target_feature = "aes", target_feature = "sse2", target_feature = "ssse3", any(target_arch = "x86_64", target_arch = "x86"))))'.dependencies] ctr = { version = "0.5", path = "../ctr" } @@ -22,4 +22,4 @@ aes-soft = "0.5" aesni = "0.9" [dev-dependencies] -stream-cipher = { version = "0.7", features = ["dev"] } +stream-cipher = { version = "0.8.0-pre", features = ["dev"] } diff --git a/ctr/Cargo.toml b/ctr/Cargo.toml index 006eeb98..3c1d8141 100644 --- a/ctr/Cargo.toml +++ b/ctr/Cargo.toml @@ -12,8 +12,9 @@ readme = "README.md" edition = "2018" [dependencies] -stream-cipher = { version = "0.7", features = ["block-cipher"] } +stream-cipher = { version = "0.8.0-pre", features = ["block-cipher"] } [dev-dependencies] aes = "0.5" -stream-cipher = { version = "0.7", features = ["block-cipher", "dev"] } +hex-literal = "0.2" +stream-cipher = { version = "0.8.0-pre", features = ["block-cipher", "dev"] } diff --git a/ctr/src/ctr128.rs b/ctr/src/ctr128.rs new file mode 100644 index 00000000..0fc4b967 --- /dev/null +++ b/ctr/src/ctr128.rs @@ -0,0 +1,201 @@ +//! Generic implementation of CTR mode for block cipher with 128-bit block size. + +use core::{convert::TryInto, fmt, mem}; +use stream_cipher::{ + block_cipher::{BlockCipher, NewBlockCipher}, + generic_array::{ + typenum::{Unsigned, U16}, + ArrayLength, GenericArray, + }, + FromBlockCipher, LoopError, OverflowError, SeekNum, SyncStreamCipher, SyncStreamCipherSeek, +}; + +#[inline(always)] +fn xor(buf: &mut [u8], key: &[u8]) { + debug_assert_eq!(buf.len(), key.len()); + for (a, b) in buf.iter_mut().zip(key) { + *a ^= *b; + } +} + +type Block = GenericArray::BlockSize>; +type Blocks = GenericArray, ::ParBlocks>; +type Nonce = GenericArray; + +/// CTR mode of operation for 128-bit block ciphers +pub struct Ctr128 +where + C: BlockCipher, + C::ParBlocks: ArrayLength>, +{ + cipher: C, + block: Block, + nonce: [u64; 2], + counter: u64, + pos: u8, +} + +impl FromBlockCipher for Ctr128 +where + C: BlockCipher + NewBlockCipher, + C::ParBlocks: ArrayLength>, +{ + type BlockCipher = C; + type NonceSize = C::BlockSize; + + fn from_block_cipher(cipher: C, nonce: &Nonce) -> Self { + Self { + cipher, + nonce: [ + u64::from_be_bytes(nonce[..8].try_into().unwrap()), + u64::from_be_bytes(nonce[8..].try_into().unwrap()), + ], + counter: 0, + block: Default::default(), + pos: 0, + } + } +} + +impl SyncStreamCipher for Ctr128 +where + C: BlockCipher, + C::ParBlocks: ArrayLength>, +{ + fn try_apply_keystream(&mut self, mut data: &mut [u8]) -> Result<(), LoopError> { + self.check_data_len(data)?; + let bs = C::BlockSize::USIZE; + let pos = self.pos as usize; + debug_assert!(bs > pos); + + let mut counter = self.counter; + if pos != 0 { + if data.len() < bs - pos { + let n = pos + data.len(); + xor(data, &self.block[pos..n]); + self.pos = n as u8; + return Ok(()); + } else { + let (l, r) = data.split_at_mut(bs - pos); + data = r; + xor(l, &self.block[pos..]); + counter += 1; + } + } + + // Process blocks in parallel if cipher supports it + let pb = C::ParBlocks::USIZE; + let data = if pb != 1 { + let mut chunks = data.chunks_exact_mut(bs * pb); + for chunk in &mut chunks { + let blocks = self.generate_par_blocks(counter); + counter += pb as u64; + xor(chunk, to_slice::(&blocks)); + } + chunks.into_remainder() + } else { + data + }; + + let mut chunks = data.chunks_exact_mut(bs); + for chunk in &mut chunks { + xor(chunk, &self.generate_block(counter)); + counter += 1; + } + + let rem = chunks.into_remainder(); + self.pos = rem.len() as u8; + self.counter = counter; + if !rem.is_empty() { + self.block = self.generate_block(counter); + xor(rem, &self.block[..rem.len()]); + } + + Ok(()) + } +} + +impl SyncStreamCipherSeek for Ctr128 +where + C: BlockCipher, + C::ParBlocks: ArrayLength>, +{ + fn try_current_pos(&self) -> Result { + T::from_block_byte(self.counter, self.pos, C::BlockSize::U8) + } + + fn try_seek(&mut self, pos: T) -> Result<(), LoopError> { + let res = pos.to_block_byte(C::BlockSize::U8)?; + self.counter = res.0; + self.pos = res.1; + if self.pos != 0 { + self.block = self.generate_block(self.counter); + } + Ok(()) + } +} + +impl Ctr128 +where + C: BlockCipher, + C::ParBlocks: ArrayLength>, +{ + #[inline(always)] + fn generate_par_blocks(&self, counter: u64) -> Blocks { + let mut block = self.nonce; + block[1] = block[1].wrapping_add(counter); + let mut blocks: Blocks = unsafe { mem::zeroed() }; + for b in blocks.iter_mut() { + let block_be = conv_be(block); + *b = unsafe { mem::transmute_copy(&block_be) }; + block[1] = block[1].wrapping_add(1); + } + + self.cipher.encrypt_blocks(&mut blocks); + + blocks + } + + #[inline(always)] + fn generate_block(&self, counter: u64) -> Block { + let mut block = self.nonce; + block[1] = block[1].wrapping_add(counter); + let mut block: Block = unsafe { mem::transmute(conv_be(block)) }; + self.cipher.encrypt_block(&mut block); + block + } + + fn check_data_len(&self, data: &[u8]) -> Result<(), LoopError> { + let bs = C::BlockSize::USIZE; + let leftover_bytes = bs - self.pos as usize; + if data.len() < leftover_bytes { + return Ok(()); + } + let blocks = 1 + (data.len() - leftover_bytes) / bs; + self.counter + .checked_add(blocks as u64) + .ok_or(LoopError) + .map(|_| ()) + } +} + +impl fmt::Debug for Ctr128 +where + C: BlockCipher + fmt::Debug, + C::ParBlocks: ArrayLength>, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "Ctr128-{:?}", self.cipher) + } +} + +#[inline(always)] +fn conv_be(val: [u64; 2]) -> [u64; 2] { + [val[0].to_be(), val[1].to_be()] +} + +#[inline(always)] +fn to_slice(blocks: &Blocks) -> &[u8] { + let blocks_len = C::BlockSize::to_usize() * C::ParBlocks::to_usize(); + unsafe { core::slice::from_raw_parts(blocks.as_ptr() as *const u8, blocks_len) } +} diff --git a/ctr/src/ctr32.rs b/ctr/src/ctr32.rs new file mode 100644 index 00000000..03243008 --- /dev/null +++ b/ctr/src/ctr32.rs @@ -0,0 +1,425 @@ +//! Generic implementation of CTR mode with a 32-bit counter +//! (big or little endian), generic over block ciphers. + +use core::{convert::TryInto, marker::PhantomData, mem}; +use stream_cipher::{ + block_cipher::{Block, BlockCipher}, + generic_array::{typenum::Unsigned, ArrayLength, GenericArray}, + FromBlockCipher, LoopError, SyncStreamCipher, +}; + +/// Internal buffer for a given block cipher +type BlockBuffer = GenericArray, ::ParBlocks>; + +/// CTR mode with a 32-bit big endian counter. +/// +/// Used by e.g. AES-GCM. +pub struct Ctr32BE +where + B: BlockCipher, + B::ParBlocks: ArrayLength>, + Block: Copy, +{ + ctr: Ctr32, +} + +/// CTR mode with a 32-bit little endian counter. +/// +/// Used by e.g. AES-GCM-SIV. +pub struct Ctr32LE +where + B: BlockCipher, + B::ParBlocks: ArrayLength>, + Block: Copy, +{ + ctr: Ctr32, +} + +impl FromBlockCipher for Ctr32BE +where + B: BlockCipher, + B::ParBlocks: ArrayLength>, + Block: Copy, +{ + type BlockCipher = B; + type NonceSize = B::BlockSize; + + #[inline] + fn from_block_cipher(cipher: B, nonce: &Block) -> Self { + Self { + ctr: Ctr32::new(cipher, *nonce), + } + } +} + +impl FromBlockCipher for Ctr32LE +where + B: BlockCipher, + B::ParBlocks: ArrayLength>, + Block: Copy, +{ + type BlockCipher = B; + type NonceSize = B::BlockSize; + + #[inline] + fn from_block_cipher(cipher: B, nonce: &Block) -> Self { + let mut counter_block = *nonce; + counter_block[15] |= 0x80; + + Self { + ctr: Ctr32::new(cipher, counter_block), + } + } +} + +/// Implement `stream-cipher` traits for the given `Ctr32*` type +macro_rules! impl_ctr32 { + ($ctr32:tt) => { + impl SyncStreamCipher for $ctr32 + where + B: BlockCipher, + B::ParBlocks: ArrayLength>, + Block: Copy, + { + #[inline] + fn try_apply_keystream(&mut self, data: &mut [u8]) -> Result<(), LoopError> { + // TODO(tarcieri): data volume limits + self.ctr.apply_keystream(data); + Ok(()) + } + } + + impl $ctr32 + where + B: BlockCipher, + B::ParBlocks: ArrayLength>, + Block: Copy, + { + /// Seek to the given NIST SP800-38D counter value. + /// + /// Note: the serialized counter value is 1 larger than the argument value. + // TODO(tarcieri): implement `SyncStreamCipherSeek` + #[inline] + pub fn seek_ctr(&mut self, pos: u32) { + self.ctr.seek(pos); + } + + /// Get the current NIST SP800-38D counter value. + // TODO(tarcieri): implement `SyncStreamCipherSeek` + #[inline] + pub fn current_ctr(&self) -> u32 { + self.ctr.current_pos() + } + } + }; +} + +impl_ctr32!(Ctr32BE); +impl_ctr32!(Ctr32LE); + +/// Inner CTR mode implementation with a 32-bit counter, generic over +/// block ciphers and endianness. +struct Ctr32 +where + B: BlockCipher, + B::ParBlocks: ArrayLength>, + E: Endianness, + Block: Copy, +{ + /// Cipher + cipher: B, + + /// Keystream buffer + buffer: BlockBuffer, + + /// Current CTR value + counter_block: Block, + + /// Base value of the counter + base_counter: u32, + + /// Endianness + endianness: PhantomData, +} + +impl Ctr32 +where + B: BlockCipher, + B::ParBlocks: ArrayLength>, + E: Endianness, + Block: Copy, +{ + /// Instantiate a new CTR instance + pub fn new(cipher: B, counter_block: Block) -> Self { + Self { + cipher, + buffer: unsafe { mem::zeroed() }, + counter_block, + base_counter: E::get_counter(&counter_block), + endianness: PhantomData, + } + } + + /// "Seek" to the given NIST SP800-38D counter value. + #[inline] + pub fn seek(&mut self, new_counter_value: u32) { + E::set_counter( + &mut self.counter_block, + new_counter_value.wrapping_add(self.base_counter), + ); + } + + /// Get the current NIST SP800-38D counter value. + #[inline] + pub fn current_pos(&self) -> u32 { + E::get_counter(&self.counter_block).wrapping_sub(self.base_counter) + } + + /// Apply CTR keystream to the given input buffer + #[inline] + pub fn apply_keystream(&mut self, msg: &mut [u8]) { + for chunk in msg.chunks_mut(B::BlockSize::to_usize() * B::ParBlocks::to_usize()) { + self.apply_keystream_blocks(chunk); + } + } + + /// Apply `B::ParBlocks` parallel blocks of keystream to the input buffer + fn apply_keystream_blocks(&mut self, msg: &mut [u8]) { + let mut counter = E::get_counter(&self.counter_block); + let n_blocks = msg.chunks(B::BlockSize::to_usize()).count(); + debug_assert!(n_blocks <= B::ParBlocks::to_usize()); + + for block in self.buffer.iter_mut().take(n_blocks) { + *block = self.counter_block; + counter = counter.wrapping_add(1); + E::set_counter(&mut self.counter_block, counter); + } + + if n_blocks == 1 { + self.cipher.encrypt_block(&mut self.buffer[0]); + } else { + self.cipher.encrypt_blocks(&mut self.buffer); + } + + for (i, chunk) in msg.chunks_mut(B::BlockSize::to_usize()).enumerate() { + let keystream_block = &self.buffer[i]; + + for (i, byte) in chunk.iter_mut().enumerate() { + *byte ^= keystream_block[i]; + } + } + } +} + +/// Endianness-related functionality +trait Endianness { + /// Get the counter value from a block + fn get_counter(block: &Block) -> u32; + + /// Set the counter inside of a block to the given value + fn set_counter(block: &mut Block, counter: u32); +} + +/// Big endian 32-bit counter +struct BigEndian; + +impl Endianness for BigEndian { + #[inline] + fn get_counter(block: &Block) -> u32 { + let offset = B::BlockSize::to_usize() - mem::size_of::(); + u32::from_be_bytes(block[offset..].try_into().unwrap()) + } + + #[inline] + fn set_counter(block: &mut Block, value: u32) { + let offset = B::BlockSize::to_usize() - mem::size_of::(); + block[offset..].copy_from_slice(&value.to_be_bytes()); + } +} + +/// Little endian 32-bit counter +struct LittleEndian; + +impl Endianness for LittleEndian { + #[inline] + fn get_counter(block: &Block) -> u32 { + u32::from_le_bytes(block[..mem::size_of::()].try_into().unwrap()) + } + + #[inline] + fn set_counter(block: &mut Block, value: u32) { + block[..mem::size_of::()].copy_from_slice(&value.to_le_bytes()); + } +} + +/// AES-128-CTR tests +/// +/// NOTE: these test vectors were generated by first integration testing the +/// implementation in the contexts of AES-GCM and AES-GCM-SIV, with the former +/// tested against the NIST CAVS vectors, and the latter against the RFC8452 +/// test vectors. +#[cfg(test)] +mod tests { + use hex_literal::hex; + const KEY: &[u8; 16] = &hex!("000102030405060708090A0B0C0D0E0F"); + + mod be { + use super::{hex, KEY}; + use stream_cipher::{NewStreamCipher, SyncStreamCipher}; + + type Aes128Ctr = crate::Ctr32BE; + + const NONCE1: &[u8; 16] = &hex!("11111111111111111111111111111111"); + const NONCE2: &[u8; 16] = &hex!("222222222222222222222222FFFFFFFE"); + + #[test] + fn counter_incr() { + let mut ctr = Aes128Ctr::new(KEY.into(), NONCE1.into()); + assert_eq!(ctr.current_ctr(), 0); + + let mut buffer = [0u8; 64]; + ctr.apply_keystream(&mut buffer); + + assert_eq!(ctr.current_ctr(), 4); + assert_eq!( + &buffer[..], + &hex!( + "35D14E6D3E3A279CF01E343E34E7DED36EEADB04F42E2251AB4377F257856DBA + 0AB37657B9C2AA09762E518FC9395D5304E96C34CCD2F0A95CDE7321852D90C0" + )[..] + ); + } + + #[test] + fn counter_seek() { + let mut ctr = Aes128Ctr::new(KEY.into(), NONCE1.into()); + ctr.seek_ctr(1); + assert_eq!(ctr.current_ctr(), 1); + + let mut buffer = [0u8; 64]; + ctr.apply_keystream(&mut buffer); + + assert_eq!(ctr.current_ctr(), 5); + assert_eq!( + &buffer[..], + &hex!( + "6EEADB04F42E2251AB4377F257856DBA0AB37657B9C2AA09762E518FC9395D53 + 04E96C34CCD2F0A95CDE7321852D90C0F7441EAB3811A03FDBD162AEC402F5AA" + )[..] + ); + } + + #[test] + fn keystream_xor() { + let mut ctr = Aes128Ctr::new(KEY.into(), NONCE1.into()); + let mut buffer = [1u8; 64]; + + ctr.apply_keystream(&mut buffer); + assert_eq!( + &buffer[..], + &hex!( + "34D04F6C3F3B269DF11F353F35E6DFD26FEBDA05F52F2350AA4276F356846CBB + 0BB27756B8C3AB08772F508EC8385C5205E86D35CDD3F1A85DDF7220842C91C1" + )[..] + ); + } + + #[test] + fn counter_wrap() { + let mut ctr = Aes128Ctr::new(KEY.into(), NONCE2.into()); + assert_eq!(ctr.current_ctr(), 0); + + let mut buffer = [0u8; 64]; + ctr.apply_keystream(&mut buffer); + + assert_eq!(ctr.current_ctr(), 4); + assert_eq!( + &buffer[..], + &hex!( + "58FC849D1CF53C54C63E1B1D15CB3C8AAA335F72135585E9FF943F4DAC77CB63 + BD1AE8716BE69C3B4D886B222B9B4E1E67548EF896A96E2746D8CA6476D8B183" + )[..] + ); + } + } + + mod le { + use super::{hex, KEY}; + use stream_cipher::{NewStreamCipher, SyncStreamCipher}; + + type Aes128Ctr = crate::Ctr32LE; + + const NONCE1: &[u8; 16] = &hex!("11111111111111111111111111111111"); + const NONCE2: &[u8; 16] = &hex!("FEFFFFFF222222222222222222222222"); + + #[test] + fn counter_incr() { + let mut ctr = Aes128Ctr::new(KEY.into(), NONCE1.into()); + assert_eq!(ctr.current_ctr(), 0); + + let mut buffer = [0u8; 64]; + ctr.apply_keystream(&mut buffer); + + assert_eq!(ctr.current_ctr(), 4); + assert_eq!( + &buffer[..], + &hex!( + "2A0680B210CAD45E886D7EF6DAB357C9F18B39AFF6930FDB2D9FCE34261FF699 + EB96774669D24B560C9AD028C5C39C4580775A82065256B4787DC91C6942B700" + )[..] + ); + } + + #[test] + fn counter_seek() { + let mut ctr = Aes128Ctr::new(KEY.into(), NONCE1.into()); + ctr.seek_ctr(1); + assert_eq!(ctr.current_ctr(), 1); + + let mut buffer = [0u8; 64]; + ctr.apply_keystream(&mut buffer); + + assert_eq!(ctr.current_ctr(), 5); + assert_eq!( + &buffer[..], + &hex!( + "F18B39AFF6930FDB2D9FCE34261FF699EB96774669D24B560C9AD028C5C39C45 + 80775A82065256B4787DC91C6942B7001564DDA1B07DCED9201AB71BAF06905B" + )[..] + ); + } + + #[test] + fn keystream_xor() { + let mut ctr = Aes128Ctr::new(KEY.into(), NONCE1.into()); + let mut buffer = [1u8; 64]; + + ctr.apply_keystream(&mut buffer); + assert_eq!( + &buffer[..], + &hex!( + "2B0781B311CBD55F896C7FF7DBB256C8F08A38AEF7920EDA2C9ECF35271EF798 + EA97764768D34A570D9BD129C4C29D4481765B83075357B5797CC81D6843B601" + )[..] + ); + } + + #[test] + fn counter_wrap() { + let mut ctr = Aes128Ctr::new(KEY.into(), NONCE2.into()); + assert_eq!(ctr.current_ctr(), 0); + + let mut buffer = [0u8; 64]; + ctr.apply_keystream(&mut buffer); + + assert_eq!(ctr.current_ctr(), 4); + assert_eq!( + &buffer[..], + &hex!( + "A1E649D8B382293DC28375C42443BB6A226BAADC9E9CCA8214F56E07A4024E06 + 6355A0DA2E08FB00112FFA38C26189EE55DD5B0B130ED87096FE01B59A665A60" + )[..] + ); + } + } +} diff --git a/ctr/src/lib.rs b/ctr/src/lib.rs index 52f5c59b..3845b300 100644 --- a/ctr/src/lib.rs +++ b/ctr/src/lib.rs @@ -1,4 +1,4 @@ -//! Generic implementation of CTR mode for block cipher with 128-bit block size. +//! Generic implementations of CTR mode for block ciphers. //! //! Mode functionality is accessed using traits from re-exported //! [`stream-cipher`](https://docs.rs/stream-cipher) crate. @@ -7,7 +7,7 @@ //! This crate does not ensure ciphertexts are authentic! Thus ciphertext integrity //! is not verified, which can lead to serious vulnerabilities! //! -//! # Usage example +//! # `Ctr128` Usage Example //! ``` //! use ctr::stream_cipher::generic_array::GenericArray; //! use ctr::stream_cipher::{ @@ -42,204 +42,11 @@ )] #![warn(missing_docs, rust_2018_idioms)] -pub use stream_cipher; +mod ctr128; +mod ctr32; -use core::{convert::TryInto, fmt, mem}; -use stream_cipher::{ - block_cipher::{BlockCipher, NewBlockCipher}, - generic_array::{ - typenum::{Unsigned, U16}, - ArrayLength, GenericArray, - }, - FromBlockCipher, LoopError, OverflowError, SeekNum, SyncStreamCipher, SyncStreamCipherSeek, +pub use crate::{ + ctr128::Ctr128, + ctr32::{Ctr32BE, Ctr32LE}, }; - -#[inline(always)] -fn xor(buf: &mut [u8], key: &[u8]) { - debug_assert_eq!(buf.len(), key.len()); - for (a, b) in buf.iter_mut().zip(key) { - *a ^= *b; - } -} - -type Block = GenericArray::BlockSize>; -type Blocks = GenericArray, ::ParBlocks>; -type Nonce = GenericArray; - -/// CTR mode of operation for 128-bit block ciphers -pub struct Ctr128 -where - C: BlockCipher, - C::ParBlocks: ArrayLength>, -{ - cipher: C, - block: Block, - nonce: [u64; 2], - counter: u64, - pos: u8, -} - -impl FromBlockCipher for Ctr128 -where - C: BlockCipher + NewBlockCipher, - C::ParBlocks: ArrayLength>, -{ - type BlockCipher = C; - type NonceSize = C::BlockSize; - - fn from_block_cipher(cipher: C, nonce: &Nonce) -> Self { - Self { - cipher, - nonce: [ - u64::from_be_bytes(nonce[..8].try_into().unwrap()), - u64::from_be_bytes(nonce[8..].try_into().unwrap()), - ], - counter: 0, - block: Default::default(), - pos: 0, - } - } -} - -impl SyncStreamCipher for Ctr128 -where - C: BlockCipher, - C::ParBlocks: ArrayLength>, -{ - fn try_apply_keystream(&mut self, mut data: &mut [u8]) -> Result<(), LoopError> { - self.check_data_len(data)?; - let bs = C::BlockSize::USIZE; - let pos = self.pos as usize; - debug_assert!(bs > pos); - - let mut counter = self.counter; - if pos != 0 { - if data.len() < bs - pos { - let n = pos + data.len(); - xor(data, &self.block[pos..n]); - self.pos = n as u8; - return Ok(()); - } else { - let (l, r) = data.split_at_mut(bs - pos); - data = r; - xor(l, &self.block[pos..]); - counter += 1; - } - } - - // Process blocks in parallel if cipher supports it - let pb = C::ParBlocks::USIZE; - let data = if pb != 1 { - let mut chunks = data.chunks_exact_mut(bs * pb); - for chunk in &mut chunks { - let blocks = self.generate_par_blocks(counter); - counter += pb as u64; - xor(chunk, to_slice::(&blocks)); - } - chunks.into_remainder() - } else { - data - }; - - let mut chunks = data.chunks_exact_mut(bs); - for chunk in &mut chunks { - xor(chunk, &self.generate_block(counter)); - counter += 1; - } - - let rem = chunks.into_remainder(); - self.pos = rem.len() as u8; - self.counter = counter; - if !rem.is_empty() { - self.block = self.generate_block(counter); - xor(rem, &self.block[..rem.len()]); - } - - Ok(()) - } -} - -impl SyncStreamCipherSeek for Ctr128 -where - C: BlockCipher, - C::ParBlocks: ArrayLength>, -{ - fn try_current_pos(&self) -> Result { - T::from_block_byte(self.counter, self.pos, C::BlockSize::U8) - } - - fn try_seek(&mut self, pos: T) -> Result<(), LoopError> { - let res = pos.to_block_byte(C::BlockSize::U8)?; - self.counter = res.0; - self.pos = res.1; - if self.pos != 0 { - self.block = self.generate_block(self.counter); - } - Ok(()) - } -} - -impl Ctr128 -where - C: BlockCipher, - C::ParBlocks: ArrayLength>, -{ - #[inline(always)] - fn generate_par_blocks(&self, counter: u64) -> Blocks { - let mut block = self.nonce; - block[1] = block[1].wrapping_add(counter); - let mut blocks: Blocks = unsafe { mem::zeroed() }; - for b in blocks.iter_mut() { - let block_be = conv_be(block); - *b = unsafe { mem::transmute_copy(&block_be) }; - block[1] = block[1].wrapping_add(1); - } - - self.cipher.encrypt_blocks(&mut blocks); - - blocks - } - - #[inline(always)] - fn generate_block(&self, counter: u64) -> Block { - let mut block = self.nonce; - block[1] = block[1].wrapping_add(counter); - let mut block: Block = unsafe { mem::transmute(conv_be(block)) }; - self.cipher.encrypt_block(&mut block); - block - } - - fn check_data_len(&self, data: &[u8]) -> Result<(), LoopError> { - let bs = C::BlockSize::USIZE; - let leftover_bytes = bs - self.pos as usize; - if data.len() < leftover_bytes { - return Ok(()); - } - let blocks = 1 + (data.len() - leftover_bytes) / bs; - self.counter - .checked_add(blocks as u64) - .ok_or(LoopError) - .map(|_| ()) - } -} - -impl fmt::Debug for Ctr128 -where - C: BlockCipher + fmt::Debug, - C::ParBlocks: ArrayLength>, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "Ctr128-{:?}", self.cipher) - } -} - -#[inline(always)] -fn conv_be(val: [u64; 2]) -> [u64; 2] { - [val[0].to_be(), val[1].to_be()] -} - -#[inline(always)] -fn to_slice(blocks: &Blocks) -> &[u8] { - let blocks_len = C::BlockSize::to_usize() * C::ParBlocks::to_usize(); - unsafe { core::slice::from_raw_parts(blocks.as_ptr() as *const u8, blocks_len) } -} +pub use stream_cipher;