From ff88e90e829fb77855c979ae5ae6a711a14f76f6 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Wed, 15 Jan 2020 20:32:42 -0800 Subject: [PATCH] chacha20: Add ChaCha8 and ChaCha20 reduced round variants See the writeup I've already done for the `rand_chacha` crate for the rationale of including these by default: https://github.com/rust-random/rand/issues/932 tl;dr: the "Too Much Crypto" paper goes into why ChaCha20 is overkill, ChaCha12 is probably what should've been standardized per the eSTREAM analysis of Salsa20 (but wasn't for cargo cult reasons), and ChaCha8 is still probably safe: https://eprint.iacr.org/2019/1492 --- chacha20/src/lib.rs | 102 ++++++++++++++++++++-------------- chacha20/src/rng.rs | 130 ++++++++++++++++++++++++++------------------ 2 files changed, 138 insertions(+), 94 deletions(-) diff --git a/chacha20/src/lib.rs b/chacha20/src/lib.rs index 3271b75d..618b9f74 100644 --- a/chacha20/src/lib.rs +++ b/chacha20/src/lib.rs @@ -12,6 +12,7 @@ //! //! - `ChaCha20`: standard IETF variant with 96-bit nonce //! - `ChaCha20Legacy`: (gated under the `legacy` feature) "djb" variant with 64-bit nonce +//! - `ChaCha8` / `ChaCha12`: reduced round variants of ChaCha20 //! - `XChaCha20`: (gated under the `xchacha20` feature) 192-bit extended nonce variant //! //! # Security Warning @@ -86,7 +87,9 @@ use stream_cipher::{LoopError, NewStreamCipher, SyncStreamCipher, SyncStreamCiph pub use self::xchacha20::XChaCha20; #[cfg(feature = "rng")] -pub use rng::{ChaCha20Rng, ChaCha20RngCore}; +pub use rng::{ + ChaCha12Rng, ChaCha12RngCore, ChaCha20Rng, ChaCha20RngCore, ChaCha8Rng, ChaCha8RngCore, +}; /// Size of a ChaCha20 block in bytes pub const BLOCK_SIZE: usize = 64; @@ -108,49 +111,66 @@ const STATE_WORDS: usize = 16; //pub(crate) const SIGMA: &[u8; 16] = b"expand 32-byte k"; const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574]; -/// The ChaCha20 stream cipher (RFC 8439 version with 96-bit nonce) -/// -/// Use `ChaCha20Legacy` for the legacy (a.k.a. "djb") construction with a -/// 64-bit nonce. -#[cfg(feature = "stream-cipher")] -pub struct ChaCha20(Cipher); - -#[cfg(feature = "stream-cipher")] -impl NewStreamCipher for ChaCha20 { - /// Key size in bytes - type KeySize = U32; - - /// Nonce size in bytes - type NonceSize = U12; - - fn new(key: &GenericArray, iv: &GenericArray) -> Self { - let block = Block::new( - key.as_ref().try_into().unwrap(), - iv[4..12].try_into().unwrap(), - 20, - ); - let counter = initial_counter(iv[..4].try_into().unwrap()); - ChaCha20(Cipher::new(block, counter)) +macro_rules! impl_chacha { + ($name:ident, $rounds:expr, $doc:expr) => { + #[cfg(feature = "stream-cipher")] + #[doc = $doc] + pub struct $name(Cipher); + + #[cfg(feature = "stream-cipher")] + impl NewStreamCipher for $name { + /// Key size in bytes + type KeySize = U32; + + /// Nonce size in bytes + type NonceSize = U12; + + fn new(key: &GenericArray, iv: &GenericArray) -> Self { + let block = Block::new( + key.as_ref().try_into().unwrap(), + iv[4..12].try_into().unwrap(), + $rounds, + ); + let counter = initial_counter(iv[..4].try_into().unwrap()); + $name(Cipher::new(block, counter)) + } + } + + #[cfg(feature = "stream-cipher")] + impl SyncStreamCipher for $name { + fn try_apply_keystream(&mut self, data: &mut [u8]) -> Result<(), LoopError> { + self.0.try_apply_keystream(data) + } + } + + #[cfg(feature = "stream-cipher")] + impl SyncStreamCipherSeek for $name { + fn current_pos(&self) -> u64 { + self.0.current_pos() + } + + fn seek(&mut self, pos: u64) { + self.0.seek(pos); + } + } } } -#[cfg(feature = "stream-cipher")] -impl SyncStreamCipher for ChaCha20 { - fn try_apply_keystream(&mut self, data: &mut [u8]) -> Result<(), LoopError> { - self.0.try_apply_keystream(data) - } -} - -#[cfg(feature = "stream-cipher")] -impl SyncStreamCipherSeek for ChaCha20 { - fn current_pos(&self) -> u64 { - self.0.current_pos() - } - - fn seek(&mut self, pos: u64) { - self.0.seek(pos); - } -} +impl_chacha!( + ChaCha8, + 8, + "The ChaCha8 stream cipher (8-round variant of ChaCha20)" +); +impl_chacha!( + ChaCha12, + 12, + "The ChaCha20 stream cipher (12-round variant of ChaCha20)" +); +impl_chacha!( + ChaCha20, + 20, + "The ChaCha20 stream cipher (RFC 8439 version with 96-bit nonce)" +); /// Get initial counter value for the given IV prefix #[cfg(feature = "stream-cipher")] diff --git a/chacha20/src/rng.rs b/chacha20/src/rng.rs index 1204c5db..e6b59fff 100644 --- a/chacha20/src/rng.rs +++ b/chacha20/src/rng.rs @@ -6,69 +6,93 @@ use rand_core::{Error, RngCore, SeedableRng}; use crate::{block::Block, BLOCK_SIZE, KEY_SIZE, STATE_WORDS}; -/// Random number generator over the ChaCha20 stream cipher. -#[derive(Clone, Debug)] -pub struct ChaCha20Rng(BlockRng); +macro_rules! impl_chacha_rng { + ($name:ident, $core:ident, $rounds:expr, $doc:expr) => { + #[doc = $doc] + #[derive(Clone, Debug)] + pub struct $name(BlockRng<$core>); -impl SeedableRng for ChaCha20Rng { - type Seed = [u8; KEY_SIZE]; + impl SeedableRng for $name { + type Seed = [u8; KEY_SIZE]; - #[inline] - fn from_seed(seed: Self::Seed) -> Self { - let core = ChaCha20RngCore::from_seed(seed); - Self(BlockRng::new(core)) - } -} + #[inline] + fn from_seed(seed: Self::Seed) -> Self { + let core = $core::from_seed(seed); + Self(BlockRng::new(core)) + } + } -impl RngCore for ChaCha20Rng { - #[inline] - fn next_u32(&mut self) -> u32 { - self.0.next_u32() - } + impl RngCore for $name { + #[inline] + fn next_u32(&mut self) -> u32 { + self.0.next_u32() + } - #[inline] - fn next_u64(&mut self) -> u64 { - self.0.next_u64() - } + #[inline] + fn next_u64(&mut self) -> u64 { + self.0.next_u64() + } - #[inline] - fn fill_bytes(&mut self, bytes: &mut [u8]) { - self.0.fill_bytes(bytes) - } + #[inline] + fn fill_bytes(&mut self, bytes: &mut [u8]) { + self.0.fill_bytes(bytes) + } - #[inline] - fn try_fill_bytes(&mut self, bytes: &mut [u8]) -> Result<(), Error> { - self.0.try_fill_bytes(bytes) - } -} + #[inline] + fn try_fill_bytes(&mut self, bytes: &mut [u8]) -> Result<(), Error> { + self.0.try_fill_bytes(bytes) + } + } -/// Core of the [`ChaCha20Rng`] random number generator, for use with -/// [`rand_core::block::BlockRng`]. -#[derive(Clone, Debug)] -pub struct ChaCha20RngCore { - block: Block, - counter: u64, -} + #[doc = "Core random number generator, for use with [`rand_core::block::BlockRng`]"] + #[derive(Clone, Debug)] + pub struct $core { + block: Block, + counter: u64, + } -impl SeedableRng for ChaCha20RngCore { - type Seed = [u8; KEY_SIZE]; + impl SeedableRng for $core { + type Seed = [u8; KEY_SIZE]; - #[inline] - fn from_seed(seed: Self::Seed) -> Self { - let block = Block::new(&seed, Default::default(), 20); - Self { block, counter: 0 } - } -} + #[inline] + fn from_seed(seed: Self::Seed) -> Self { + let block = Block::new(&seed, Default::default(), $rounds); + Self { block, counter: 0 } + } + } -impl BlockRngCore for ChaCha20RngCore { - type Item = u32; - type Results = [u32; STATE_WORDS]; + impl BlockRngCore for $core { + type Item = u32; + type Results = [u32; STATE_WORDS]; - fn generate(&mut self, results: &mut Self::Results) { - // TODO(tarcieri): eliminate unsafety (replace w\ [u8; BLOCK_SIZE) - self.block.generate(self.counter, unsafe { - slice::from_raw_parts_mut(results.as_mut_ptr() as *mut u8, BLOCK_SIZE) - }); - self.counter += 1; + fn generate(&mut self, results: &mut Self::Results) { + // TODO(tarcieri): eliminate unsafety (replace w\ [u8; BLOCK_SIZE) + self.block.generate(self.counter, unsafe { + slice::from_raw_parts_mut(results.as_mut_ptr() as *mut u8, BLOCK_SIZE) + }); + self.counter += 1; + } + } } } + +impl_chacha_rng!( + ChaCha8Rng, + ChaCha8RngCore, + 8, + "Random number generator over the ChaCha8 stream cipher." +); + +impl_chacha_rng!( + ChaCha12Rng, + ChaCha12RngCore, + 12, + "Random number generator over the ChaCha12 stream cipher." +); + +impl_chacha_rng!( + ChaCha20Rng, + ChaCha20RngCore, + 20, + "Random number generator over the ChaCha20 stream cipher." +);