chacha20/
lib.rs

1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![doc = include_str!("../README.md")]
4#![doc(
5    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
6    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
7)]
8
9//! # Usage
10//!
11//! Cipher functionality is accessed using traits from re-exported [`cipher`] crate, or as a set
12//! of random number generator types ending in `*Rng` which implement traits from the [`rand_core`]
13//! crate.
14//!
15//! This crate contains the following variants of the ChaCha20 core algorithm:
16//!
17//! - [`ChaCha20`]: standard IETF variant with 96-bit nonce
18//! - [`ChaCha8`] / [`ChaCha12`]: reduced round variants of ChaCha20
19//! - [`XChaCha20`]: 192-bit extended nonce variant
20//! - [`XChaCha8`] / [`XChaCha12`]: reduced round variants of XChaCha20
21//! - [`ChaCha20Legacy`]: "djb" variant with 64-bit nonce.
22//! **WARNING:** This implementation internally uses 32-bit counter,
23//! while the original implementation uses 64-bit counter. In other words,
24//! it does not allow encryption of more than 256 GiB of data.
25//!
26//! ## Example
27#![cfg_attr(feature = "cipher", doc = " ```")]
28#![cfg_attr(not(feature = "cipher"), doc = " ```ignore")]
29//! use chacha20::ChaCha20;
30//! // Import relevant traits
31//! use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
32//! use hex_literal::hex;
33//!
34//! let key = [0x42; 32];
35//! let nonce = [0x24; 12];
36//! let plaintext = hex!("00010203 04050607 08090A0B 0C0D0E0F");
37//! let ciphertext = hex!("e405626e 4f1236b3 670ee428 332ea20e");
38//!
39//! // Key and IV must be references to the `Array` type.
40//! // Here we use the `Into` trait to convert arrays into it.
41//! let mut cipher = ChaCha20::new(&key.into(), &nonce.into());
42//!
43//! let mut buffer = plaintext.clone();
44//!
45//! // apply keystream (encrypt)
46//! cipher.apply_keystream(&mut buffer);
47//! assert_eq!(buffer, ciphertext);
48//!
49//! let ciphertext = buffer.clone();
50//!
51//! // ChaCha ciphers support seeking
52//! cipher.seek(0u32);
53//!
54//! // decrypt ciphertext by applying keystream again
55//! cipher.apply_keystream(&mut buffer);
56//! assert_eq!(buffer, plaintext);
57//!
58//! // stream ciphers can be used with streaming messages
59//! cipher.seek(0u32);
60//! for chunk in buffer.chunks_mut(3) {
61//!     cipher.apply_keystream(chunk);
62//! }
63//! assert_eq!(buffer, ciphertext);
64//! ```
65//!
66//! # Configuration Flags
67//!
68//! You can modify crate using the following configuration flags:
69//!
70//! - `chacha20_backend="avx2"`: force AVX2 backend on x86/x86_64 targets.
71//!   Requires enabled AVX2 target feature. Ignored on non-x86(_64) targets.
72//! - `chacha20_backend="avx512": force AVX-512 backend on x86/x86_64 targets.
73//!   Requires enabled AVX-512 target feature (MSRV 1.89). Ignored on non-x86(_64) targets.
74//! - `chacha20_backend="soft"`: force software backend.
75//! - `chacha20_backend="sse2"`: force SSE2 backend on x86/x86_64 targets.
76//!   Requires enabled SSE2 target feature. Ignored on non-x86(-64) targets.
77//!
78//! The flags can be enabled using `RUSTFLAGS` environmental variable
79//! (e.g. `RUSTFLAGS='--cfg chacha20_backend="avx2"'`) or by modifying `.cargo/config.toml`:
80//!
81//! ```toml
82//! # In .cargo/config.toml
83//! [build]
84//! rustflags = ['--cfg', 'chacha20_backend="avx2"']
85//! ```
86//!
87//! ## AVX-512 support
88//!
89//! To use the MSRV 1.89 AVX-512 support, you must enable it using: `--cfg chacha20_avx512`.
90//!
91//! [ChaCha]: https://tools.ietf.org/html/rfc8439
92//! [Salsa]: https://en.wikipedia.org/wiki/Salsa20
93//! [`chacha20poly1305`]: https://docs.rs/chacha20poly1305
94
95pub mod variants;
96
97mod backends;
98#[cfg(feature = "cipher")]
99mod chacha;
100#[cfg(feature = "legacy")]
101mod legacy;
102#[cfg(feature = "rng")]
103mod rng;
104#[cfg(feature = "xchacha")]
105mod xchacha;
106
107#[cfg(feature = "cipher")]
108pub use chacha::{ChaCha8, ChaCha12, ChaCha20, Key, KeyIvInit};
109#[cfg(feature = "cipher")]
110pub use cipher;
111#[cfg(feature = "legacy")]
112pub use legacy::{ChaCha20Legacy, LegacyNonce};
113#[cfg(feature = "rng")]
114pub use rand_core;
115#[cfg(feature = "rng")]
116pub use rng::{ChaCha8Rng, ChaCha12Rng, ChaCha20Rng, Seed, SerializedRngState};
117#[cfg(feature = "xchacha")]
118pub use xchacha::{XChaCha8, XChaCha12, XChaCha20, XNonce, hchacha};
119
120use cfg_if::cfg_if;
121use core::{fmt, marker::PhantomData};
122use variants::Variant;
123
124#[cfg(feature = "cipher")]
125use cipher::{BlockSizeUser, StreamCipherCore, StreamCipherSeekCore, consts::U64};
126#[cfg(feature = "zeroize")]
127use zeroize::{Zeroize, ZeroizeOnDrop};
128
129/// State initialization constant ("expand 32-byte k")
130#[cfg(any(feature = "cipher", feature = "rng"))]
131const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];
132
133/// Number of 32-bit words in the ChaCha state
134const STATE_WORDS: usize = 16;
135
136/// Marker type for a number of ChaCha rounds to perform.
137pub trait Rounds: Copy {
138    /// The amount of rounds to perform
139    const COUNT: usize;
140}
141
142/// 8-rounds
143#[derive(Copy, Clone, Debug)]
144pub struct R8;
145
146impl Rounds for R8 {
147    const COUNT: usize = 4;
148}
149
150/// 12-rounds
151#[derive(Copy, Clone, Debug)]
152pub struct R12;
153
154impl Rounds for R12 {
155    const COUNT: usize = 6;
156}
157
158/// 20-rounds
159#[derive(Copy, Clone, Debug)]
160pub struct R20;
161
162impl Rounds for R20 {
163    const COUNT: usize = 10;
164}
165
166cfg_if! {
167    if #[cfg(chacha20_backend = "soft")] {
168        type Tokens = ();
169    } else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
170        cfg_if! {
171            if #[cfg(all(chacha20_avx512, chacha20_backend = "avx512"))] {
172                #[cfg(not(all(target_feature = "avx512f", target_feature = "avx512vl")))]
173                compile_error!("You must enable `avx512f` and `avx512vl` target features with \
174                    `chacha20_backend = "avx512"` configuration option");
175                type Tokens = ();
176            } else if #[cfg(chacha20_backend = "avx2")] {
177                #[cfg(not(target_feature = "avx2"))]
178                compile_error!("You must enable `avx2` target feature with \
179                    `chacha20_backend = "avx2"` configuration option");
180                type Tokens = ();
181            } else if #[cfg(chacha20_backend = "sse2")] {
182                #[cfg(not(target_feature = "sse2"))]
183                compile_error!("You must enable `sse2` target feature with \
184                    `chacha20_backend = "sse2"` configuration option");
185                type Tokens = ();
186            } else {
187                #[cfg(chacha20_avx512)]
188                cpufeatures::new!(avx512_cpuid, "avx512f", "avx512vl");
189                cpufeatures::new!(avx2_cpuid, "avx2");
190                cpufeatures::new!(sse2_cpuid, "sse2");
191                #[cfg(chacha20_avx512)]
192                type Tokens = (avx512_cpuid::InitToken, avx2_cpuid::InitToken, sse2_cpuid::InitToken);
193                #[cfg(not(chacha20_avx512))]
194                type Tokens = (avx2_cpuid::InitToken, sse2_cpuid::InitToken);
195            }
196        }
197    } else {
198        type Tokens = ();
199    }
200}
201
202/// The ChaCha core function.
203pub struct ChaChaCore<R: Rounds, V: Variant> {
204    /// Internal state of the core function
205    state: [u32; STATE_WORDS],
206    /// CPU target feature tokens
207    #[allow(dead_code)]
208    tokens: Tokens,
209    /// Number of rounds to perform and the cipher variant
210    _pd: PhantomData<(R, V)>,
211}
212
213impl<R: Rounds, V: Variant> ChaChaCore<R, V> {
214    /// Constructs a ChaChaCore with the specified `key` and `iv`.
215    ///
216    /// You must ensure that the iv is of the correct size when using this method
217    /// directly.
218    ///
219    /// # Panics
220    /// If `iv.len()` is not equal to 4, 8, or 12.
221    #[must_use]
222    #[cfg(any(feature = "cipher", feature = "rng"))]
223    fn new_internal(key: &[u8; 32], iv: &[u8]) -> Self {
224        assert!(matches!(iv.len(), 4 | 8 | 12));
225
226        let mut state = [0u32; STATE_WORDS];
227
228        let ctr_size = size_of::<V::Counter>() / size_of::<u32>();
229        let (const_dst, state_rem) = state.split_at_mut(4);
230        let (key_dst, state_rem) = state_rem.split_at_mut(8);
231        let (_ctr_dst, iv_dst) = state_rem.split_at_mut(ctr_size);
232
233        const_dst.copy_from_slice(&CONSTANTS);
234
235        // TODO(tarcieri): when MSRV 1.88, use `[T]::as_chunks` to avoid panic
236        #[allow(clippy::unwrap_used, reason = "MSRV TODO")]
237        {
238            for (src, dst) in key.chunks_exact(4).zip(key_dst) {
239                *dst = u32::from_le_bytes(src.try_into().unwrap());
240            }
241
242            assert_eq!(size_of_val(iv_dst), size_of_val(iv));
243            for (src, dst) in iv.chunks_exact(4).zip(iv_dst) {
244                *dst = u32::from_le_bytes(src.try_into().unwrap());
245            }
246        }
247
248        cfg_if! {
249            if #[cfg(chacha20_backend = "soft")] {
250                let tokens = ();
251            } else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
252                cfg_if! {
253                    if #[cfg(chacha20_backend = "avx512")] {
254                        let tokens = ();
255                    } else if #[cfg(chacha20_backend = "avx2")] {
256                        let tokens = ();
257                    } else if #[cfg(chacha20_backend = "sse2")] {
258                        let tokens = ();
259                    } else if #[cfg(chacha20_avx512)] {
260                        let tokens = (avx512_cpuid::init(), avx2_cpuid::init(), sse2_cpuid::init());
261                    } else {
262                        let tokens = (avx2_cpuid::init(), sse2_cpuid::init());
263                    }
264                }
265            } else {
266                let tokens = ();
267            }
268        }
269        Self {
270            state,
271            tokens,
272            _pd: PhantomData,
273        }
274    }
275
276    /// Get the current block position.
277    #[inline(always)]
278    #[must_use]
279    pub fn get_block_pos(&self) -> V::Counter {
280        V::get_block_pos(&self.state[12..])
281    }
282
283    /// Set the block position.
284    #[inline(always)]
285    pub fn set_block_pos(&mut self, pos: V::Counter) {
286        V::set_block_pos(&mut self.state[12..], pos);
287    }
288}
289
290impl<R: Rounds, V: Variant> fmt::Debug for ChaChaCore<R, V> {
291    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292        write!(
293            f,
294            "ChaChaCore<R: {}, V: {}-bit)> {{ ... }}",
295            R::COUNT,
296            size_of::<V::Counter>() * 8
297        )
298    }
299}
300
301#[cfg(feature = "cipher")]
302impl<R: Rounds, V: Variant> StreamCipherSeekCore for ChaChaCore<R, V> {
303    type Counter = V::Counter;
304
305    #[inline(always)]
306    fn get_block_pos(&self) -> Self::Counter {
307        self.get_block_pos()
308    }
309
310    #[inline(always)]
311    fn set_block_pos(&mut self, pos: Self::Counter) {
312        self.set_block_pos(pos);
313    }
314}
315
316#[cfg(feature = "cipher")]
317impl<R: Rounds, V: Variant> StreamCipherCore for ChaChaCore<R, V> {
318    #[inline(always)]
319    fn remaining_blocks(&self) -> Option<usize> {
320        V::remaining_blocks(self.get_block_pos())
321    }
322
323    fn process_with_backend(
324        &mut self,
325        f: impl cipher::StreamCipherClosure<BlockSize = Self::BlockSize>,
326    ) {
327        cfg_if! {
328            if #[cfg(chacha20_backend = "soft")] {
329                f.call(&mut backends::soft::Backend(self));
330            } else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
331                cfg_if! {
332                    if #[cfg(all(chacha20_avx512, chacha20_backend = "avx512"))] {
333                        unsafe {
334                            backends::avx512::inner::<R, _, V>(&mut self.state, f);
335                        }
336                    } else if #[cfg(chacha20_backend = "avx2")] {
337                        unsafe {
338                            backends::avx2::inner::<R, _, V>(&mut self.state, f);
339                        }
340                    } else if #[cfg(chacha20_backend = "sse2")] {
341                        unsafe {
342                            backends::sse2::inner::<R, _, V>(&mut self.state, f);
343                        }
344                    } else {
345                        #[cfg(chacha20_avx512)]
346                        let (avx512_token, avx2_token, sse2_token) = self.tokens;
347                        #[cfg(not(chacha20_avx512))]
348                        let (avx2_token, sse2_token) = self.tokens;
349
350                        #[cfg(chacha20_avx512)]
351                        if avx512_token.get() {
352                            // SAFETY: runtime CPU feature detection above ensures this is valid
353                            unsafe {
354                                backends::avx512::inner::<R, _, V>(&mut self.state, f);
355                            }
356                            return;
357                        }
358                        if avx2_token.get() {
359                            // SAFETY: runtime CPU feature detection above ensures this is valid
360                            unsafe {
361                                backends::avx2::inner::<R, _, V>(&mut self.state, f);
362                            }
363                        } else if sse2_token.get() {
364                            // SAFETY: runtime CPU feature detection above ensures this is valid
365                            unsafe {
366                                backends::sse2::inner::<R, _, V>(&mut self.state, f);
367                            }
368                        } else {
369                            f.call(&mut backends::soft::Backend(self));
370                        }
371                    }
372                }
373            } else if #[cfg(all(target_arch = "aarch64", target_feature = "neon"))] {
374                // SAFETY: we have used conditional compilation to ensure NEON is available
375                unsafe {
376                    backends::neon::inner::<R, _, V>(&mut self.state, f);
377                }
378            } else {
379                f.call(&mut backends::soft::Backend(self));
380            }
381        }
382    }
383}
384
385#[cfg(feature = "cipher")]
386impl<R: Rounds, V: Variant> BlockSizeUser for ChaChaCore<R, V> {
387    type BlockSize = U64;
388}
389
390#[cfg(feature = "zeroize")]
391impl<R: Rounds, V: Variant> Drop for ChaChaCore<R, V> {
392    fn drop(&mut self) {
393        self.state.zeroize();
394    }
395}
396
397#[cfg(feature = "zeroize")]
398impl<R: Rounds, V: Variant> ZeroizeOnDrop for ChaChaCore<R, V> {}
399
400/// The ChaCha20 quarter round function
401///
402/// We located this function in the root of the crate as we want it to be available
403/// for the soft backend and for xchacha.
404#[allow(dead_code)]
405pub(crate) fn quarter_round(
406    a: usize,
407    b: usize,
408    c: usize,
409    d: usize,
410    state: &mut [u32; STATE_WORDS],
411) {
412    state[a] = state[a].wrapping_add(state[b]);
413    state[d] ^= state[a];
414    state[d] = state[d].rotate_left(16);
415
416    state[c] = state[c].wrapping_add(state[d]);
417    state[b] ^= state[c];
418    state[b] = state[b].rotate_left(12);
419
420    state[a] = state[a].wrapping_add(state[b]);
421    state[d] ^= state[a];
422    state[d] = state[d].rotate_left(8);
423
424    state[c] = state[c].wrapping_add(state[d]);
425    state[b] ^= state[c];
426    state[b] = state[b].rotate_left(7);
427}