Skip to content

Commit 884a258

Browse files
committed
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: rust-random/rand#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
1 parent 8974f05 commit 884a258

2 files changed

Lines changed: 138 additions & 94 deletions

File tree

‎chacha20/src/lib.rs‎

Lines changed: 61 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//!
1313
//! - `ChaCha20`: standard IETF variant with 96-bit nonce
1414
//! - `ChaCha20Legacy`: (gated under the `legacy` feature) "djb" variant with 64-bit nonce
15+
//! - `ChaCha8` / `ChaCha12`: reduced round variants of ChaCha20
1516
//! - `XChaCha20`: (gated under the `xchacha20` feature) 192-bit extended nonce variant
1617
//!
1718
//! # Security Warning
@@ -86,7 +87,9 @@ use stream_cipher::{LoopError, NewStreamCipher, SyncStreamCipher, SyncStreamCiph
8687
pub use self::xchacha20::XChaCha20;
8788

8889
#[cfg(feature = "rng")]
89-
pub use rng::{ChaCha20Rng, ChaCha20RngCore};
90+
pub use rng::{
91+
ChaCha12Rng, ChaCha12RngCore, ChaCha20Rng, ChaCha20RngCore, ChaCha8Rng, ChaCha8RngCore,
92+
};
9093

9194
/// Size of a ChaCha20 block in bytes
9295
pub const BLOCK_SIZE: usize = 64;
@@ -108,49 +111,66 @@ const STATE_WORDS: usize = 16;
108111
//pub(crate) const SIGMA: &[u8; 16] = b"expand 32-byte k";
109112
const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];
110113

111-
/// The ChaCha20 stream cipher (RFC 8439 version with 96-bit nonce)
112-
///
113-
/// Use `ChaCha20Legacy` for the legacy (a.k.a. "djb") construction with a
114-
/// 64-bit nonce.
115-
#[cfg(feature = "stream-cipher")]
116-
pub struct ChaCha20(Cipher);
117-
118-
#[cfg(feature = "stream-cipher")]
119-
impl NewStreamCipher for ChaCha20 {
120-
/// Key size in bytes
121-
type KeySize = U32;
122-
123-
/// Nonce size in bytes
124-
type NonceSize = U12;
125-
126-
fn new(key: &GenericArray<u8, U32>, iv: &GenericArray<u8, U12>) -> Self {
127-
let block = Block::new(
128-
key.as_ref().try_into().unwrap(),
129-
iv[4..12].try_into().unwrap(),
130-
20,
131-
);
132-
let counter = initial_counter(iv[..4].try_into().unwrap());
133-
ChaCha20(Cipher::new(block, counter))
114+
macro_rules! impl_chacha {
115+
($name:ident, $rounds:expr, $doc:expr) => {
116+
#[cfg(feature = "stream-cipher")]
117+
#[doc = $doc]
118+
pub struct $name(Cipher);
119+
120+
#[cfg(feature = "stream-cipher")]
121+
impl NewStreamCipher for $name {
122+
/// Key size in bytes
123+
type KeySize = U32;
124+
125+
/// Nonce size in bytes
126+
type NonceSize = U12;
127+
128+
fn new(key: &GenericArray<u8, U32>, iv: &GenericArray<u8, U12>) -> Self {
129+
let block = Block::new(
130+
key.as_ref().try_into().unwrap(),
131+
iv[4..12].try_into().unwrap(),
132+
$rounds,
133+
);
134+
let counter = initial_counter(iv[..4].try_into().unwrap());
135+
$name(Cipher::new(block, counter))
136+
}
137+
}
138+
139+
#[cfg(feature = "stream-cipher")]
140+
impl SyncStreamCipher for $name {
141+
fn try_apply_keystream(&mut self, data: &mut [u8]) -> Result<(), LoopError> {
142+
self.0.try_apply_keystream(data)
143+
}
144+
}
145+
146+
#[cfg(feature = "stream-cipher")]
147+
impl SyncStreamCipherSeek for $name {
148+
fn current_pos(&self) -> u64 {
149+
self.0.current_pos()
150+
}
151+
152+
fn seek(&mut self, pos: u64) {
153+
self.0.seek(pos);
154+
}
155+
}
134156
}
135157
}
136158

137-
#[cfg(feature = "stream-cipher")]
138-
impl SyncStreamCipher for ChaCha20 {
139-
fn try_apply_keystream(&mut self, data: &mut [u8]) -> Result<(), LoopError> {
140-
self.0.try_apply_keystream(data)
141-
}
142-
}
143-
144-
#[cfg(feature = "stream-cipher")]
145-
impl SyncStreamCipherSeek for ChaCha20 {
146-
fn current_pos(&self) -> u64 {
147-
self.0.current_pos()
148-
}
149-
150-
fn seek(&mut self, pos: u64) {
151-
self.0.seek(pos);
152-
}
153-
}
159+
impl_chacha!(
160+
ChaCha8,
161+
8,
162+
"The ChaCha8 stream cipher (8-round variant of ChaCha20)"
163+
);
164+
impl_chacha!(
165+
ChaCha12,
166+
12,
167+
"The ChaCha20 stream cipher (12-round variant of ChaCha20)"
168+
);
169+
impl_chacha!(
170+
ChaCha20,
171+
20,
172+
"The ChaCha20 stream cipher (RFC 8439 version with 96-bit nonce)"
173+
);
154174

155175
/// Get initial counter value for the given IV prefix
156176
#[cfg(feature = "stream-cipher")]

‎chacha20/src/rng.rs‎

Lines changed: 77 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,69 +6,93 @@ use rand_core::{Error, RngCore, SeedableRng};
66

77
use crate::{block::Block, BLOCK_SIZE, KEY_SIZE, STATE_WORDS};
88

9-
/// Random number generator over the ChaCha20 stream cipher.
10-
#[derive(Clone, Debug)]
11-
pub struct ChaCha20Rng(BlockRng<ChaCha20RngCore>);
9+
macro_rules! impl_chacha_rng {
10+
($name:ident, $core:ident, $rounds:expr, $doc:expr) => {
11+
#[doc = $doc]
12+
#[derive(Clone, Debug)]
13+
pub struct $name(BlockRng<$core>);
1214

13-
impl SeedableRng for ChaCha20Rng {
14-
type Seed = [u8; KEY_SIZE];
15+
impl SeedableRng for $name {
16+
type Seed = [u8; KEY_SIZE];
1517

16-
#[inline]
17-
fn from_seed(seed: Self::Seed) -> Self {
18-
let core = ChaCha20RngCore::from_seed(seed);
19-
Self(BlockRng::new(core))
20-
}
21-
}
18+
#[inline]
19+
fn from_seed(seed: Self::Seed) -> Self {
20+
let core = $core::from_seed(seed);
21+
Self(BlockRng::new(core))
22+
}
23+
}
2224

23-
impl RngCore for ChaCha20Rng {
24-
#[inline]
25-
fn next_u32(&mut self) -> u32 {
26-
self.0.next_u32()
27-
}
25+
impl RngCore for $name {
26+
#[inline]
27+
fn next_u32(&mut self) -> u32 {
28+
self.0.next_u32()
29+
}
2830

29-
#[inline]
30-
fn next_u64(&mut self) -> u64 {
31-
self.0.next_u64()
32-
}
31+
#[inline]
32+
fn next_u64(&mut self) -> u64 {
33+
self.0.next_u64()
34+
}
3335

34-
#[inline]
35-
fn fill_bytes(&mut self, bytes: &mut [u8]) {
36-
self.0.fill_bytes(bytes)
37-
}
36+
#[inline]
37+
fn fill_bytes(&mut self, bytes: &mut [u8]) {
38+
self.0.fill_bytes(bytes)
39+
}
3840

39-
#[inline]
40-
fn try_fill_bytes(&mut self, bytes: &mut [u8]) -> Result<(), Error> {
41-
self.0.try_fill_bytes(bytes)
42-
}
43-
}
41+
#[inline]
42+
fn try_fill_bytes(&mut self, bytes: &mut [u8]) -> Result<(), Error> {
43+
self.0.try_fill_bytes(bytes)
44+
}
45+
}
4446

45-
/// Core of the [`ChaCha20Rng`] random number generator, for use with
46-
/// [`rand_core::block::BlockRng`].
47-
#[derive(Clone, Debug)]
48-
pub struct ChaCha20RngCore {
49-
block: Block,
50-
counter: u64,
51-
}
47+
#[doc = "Core random number generator, for use with [`rand_core::block::BlockRng`]"]
48+
#[derive(Clone, Debug)]
49+
pub struct $core {
50+
block: Block,
51+
counter: u64,
52+
}
5253

53-
impl SeedableRng for ChaCha20RngCore {
54-
type Seed = [u8; KEY_SIZE];
54+
impl SeedableRng for $core {
55+
type Seed = [u8; KEY_SIZE];
5556

56-
#[inline]
57-
fn from_seed(seed: Self::Seed) -> Self {
58-
let block = Block::new(&seed, Default::default(), 20);
59-
Self { block, counter: 0 }
60-
}
61-
}
57+
#[inline]
58+
fn from_seed(seed: Self::Seed) -> Self {
59+
let block = Block::new(&seed, Default::default(), $rounds);
60+
Self { block, counter: 0 }
61+
}
62+
}
6263

63-
impl BlockRngCore for ChaCha20RngCore {
64-
type Item = u32;
65-
type Results = [u32; STATE_WORDS];
64+
impl BlockRngCore for $core {
65+
type Item = u32;
66+
type Results = [u32; STATE_WORDS];
6667

67-
fn generate(&mut self, results: &mut Self::Results) {
68-
// TODO(tarcieri): eliminate unsafety (replace w\ [u8; BLOCK_SIZE)
69-
self.block.generate(self.counter, unsafe {
70-
slice::from_raw_parts_mut(results.as_mut_ptr() as *mut u8, BLOCK_SIZE)
71-
});
72-
self.counter += 1;
68+
fn generate(&mut self, results: &mut Self::Results) {
69+
// TODO(tarcieri): eliminate unsafety (replace w\ [u8; BLOCK_SIZE)
70+
self.block.generate(self.counter, unsafe {
71+
slice::from_raw_parts_mut(results.as_mut_ptr() as *mut u8, BLOCK_SIZE)
72+
});
73+
self.counter += 1;
74+
}
75+
}
7376
}
7477
}
78+
79+
impl_chacha_rng!(
80+
ChaCha8Rng,
81+
ChaCha8RngCore,
82+
8,
83+
"Random number generator over the ChaCha8 stream cipher."
84+
);
85+
86+
impl_chacha_rng!(
87+
ChaCha12Rng,
88+
ChaCha12RngCore,
89+
12,
90+
"Random number generator over the ChaCha12 stream cipher."
91+
);
92+
93+
impl_chacha_rng!(
94+
ChaCha20Rng,
95+
ChaCha20RngCore,
96+
20,
97+
"Random number generator over the ChaCha20 stream cipher."
98+
);

0 commit comments

Comments
 (0)