chacha20/
lib.rs

1//! Implementation of the [ChaCha] family of stream ciphers.
2//!
3//! Cipher functionality is accessed using traits from re-exported [`cipher`] crate.
4//!
5//! ChaCha stream ciphers are lightweight and amenable to fast, constant-time
6//! implementations in software. It improves upon the previous [Salsa] design,
7//! providing increased per-round diffusion with no cost to performance.
8//!
9//! This crate contains the following variants of the ChaCha20 core algorithm:
10//!
11//! - [`ChaCha20`]: standard IETF variant with 96-bit nonce
12//! - [`ChaCha8`] / [`ChaCha12`]: reduced round variants of ChaCha20
13//! - [`XChaCha20`]: 192-bit extended nonce variant
14//! - [`XChaCha8`] / [`XChaCha12`]: reduced round variants of XChaCha20
15//! - [`ChaCha20Legacy`]: "djb" variant with 64-bit nonce.
16//! **WARNING:** This implementation internally uses 32-bit counter,
17//! while the original implementation uses 64-bit counter. In other words,
18//! it does not allow encryption of more than 256 GiB of data.
19//!
20//! # ⚠️ Security Warning: Hazmat!
21//!
22//! This crate does not ensure ciphertexts are authentic, which can lead to
23//! serious vulnerabilities if used incorrectly!
24//!
25//! If in doubt, use the [`chacha20poly1305`] crate instead, which provides
26//! an authenticated mode on top of ChaCha20.
27//!
28//! **USE AT YOUR OWN RISK!**
29//!
30//! # Diagram
31//!
32//! This diagram illustrates the ChaCha quarter round function.
33//! Each round consists of four quarter-rounds:
34//!
35//! <img src="https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/img/stream-ciphers/chacha20.png" width="300px">
36//!
37//! Legend:
38//!
39//! - ⊞ add
40//! - ‹‹‹ rotate
41//! - ⊕ xor
42//!
43//! # Example
44//! ```
45//! use chacha20::ChaCha20;
46//! // Import relevant traits
47//! use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
48//! use hex_literal::hex;
49//!
50//! let key = [0x42; 32];
51//! let nonce = [0x24; 12];
52//! let plaintext = hex!("00010203 04050607 08090A0B 0C0D0E0F");
53//! let ciphertext = hex!("e405626e 4f1236b3 670ee428 332ea20e");
54//!
55//! // Key and IV must be references to the `GenericArray` type.
56//! // Here we use the `Into` trait to convert arrays into it.
57//! let mut cipher = ChaCha20::new(&key.into(), &nonce.into());
58//!
59//! let mut buffer = plaintext.clone();
60//!
61//! // apply keystream (encrypt)
62//! cipher.apply_keystream(&mut buffer);
63//! assert_eq!(buffer, ciphertext);
64//!
65//! let ciphertext = buffer.clone();
66//!
67//! // ChaCha ciphers support seeking
68//! cipher.seek(0u32);
69//!
70//! // decrypt ciphertext by applying keystream again
71//! cipher.apply_keystream(&mut buffer);
72//! assert_eq!(buffer, plaintext);
73//!
74//! // stream ciphers can be used with streaming messages
75//! cipher.seek(0u32);
76//! for chunk in buffer.chunks_mut(3) {
77//!     cipher.apply_keystream(chunk);
78//! }
79//! assert_eq!(buffer, ciphertext);
80//! ```
81//!
82//! # Configuration Flags
83//!
84//! You can modify crate using the following configuration flags:
85//!
86//! - `chacha20_force_avx2`: force AVX2 backend on x86/x86_64 targets.
87//!   Requires enabled AVX2 target feature. Ignored on non-x86(-64) targets.
88//! - `chacha20_force_neon`: force NEON backend on ARM targets.
89//!   Requires enabled NEON target feature. Ignored on non-ARM targets. Nightly-only.
90//! - `chacha20_force_soft`: force software backend.
91//! - `chacha20_force_sse2`: force SSE2 backend on x86/x86_64 targets.
92//!   Requires enabled SSE2 target feature. Ignored on non-x86(-64) targets.
93//!
94//! The flags can be enabled using `RUSTFLAGS` environmental variable
95//! (e.g. `RUSTFLAGS="--cfg chacha20_force_avx2"`) or by modifying `.cargo/config`.
96//!
97//! You SHOULD NOT enable several `force` flags simultaneously.
98//!
99//! [ChaCha]: https://tools.ietf.org/html/rfc8439
100//! [Salsa]: https://en.wikipedia.org/wiki/Salsa20
101//! [`chacha20poly1305`]: https://docs.rs/chacha20poly1305
102
103#![no_std]
104#![cfg_attr(docsrs, feature(doc_cfg))]
105#![doc(
106    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
107    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
108)]
109#![allow(clippy::needless_range_loop)]
110#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
111
112pub use cipher;
113
114use cfg_if::cfg_if;
115use cipher::{
116    consts::{U10, U12, U32, U4, U6, U64},
117    generic_array::{typenum::Unsigned, GenericArray},
118    BlockSizeUser, IvSizeUser, KeyIvInit, KeySizeUser, StreamCipherCore, StreamCipherCoreWrapper,
119    StreamCipherSeekCore, StreamClosure,
120};
121use core::marker::PhantomData;
122
123#[cfg(feature = "zeroize")]
124use cipher::zeroize::{Zeroize, ZeroizeOnDrop};
125
126mod backends;
127mod legacy;
128mod xchacha;
129
130pub use legacy::{ChaCha20Legacy, ChaCha20LegacyCore, LegacyNonce};
131pub use xchacha::{hchacha, XChaCha12, XChaCha20, XChaCha8, XChaChaCore, XNonce};
132
133/// State initialization constant ("expand 32-byte k")
134const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];
135
136/// Number of 32-bit words in the ChaCha state
137const STATE_WORDS: usize = 16;
138
139/// Block type used by all ChaCha variants.
140type Block = GenericArray<u8, U64>;
141
142/// Key type used by all ChaCha variants.
143pub type Key = GenericArray<u8, U32>;
144
145/// Nonce type used by ChaCha variants.
146pub type Nonce = GenericArray<u8, U12>;
147
148/// ChaCha8 stream cipher (reduced-round variant of [`ChaCha20`] with 8 rounds)
149pub type ChaCha8 = StreamCipherCoreWrapper<ChaChaCore<U4>>;
150
151/// ChaCha12 stream cipher (reduced-round variant of [`ChaCha20`] with 12 rounds)
152pub type ChaCha12 = StreamCipherCoreWrapper<ChaChaCore<U6>>;
153
154/// ChaCha20 stream cipher (RFC 8439 version with 96-bit nonce)
155pub type ChaCha20 = StreamCipherCoreWrapper<ChaChaCore<U10>>;
156
157cfg_if! {
158    if #[cfg(chacha20_force_soft)] {
159        type Tokens = ();
160    } else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
161        cfg_if! {
162            if #[cfg(chacha20_force_avx2)] {
163                #[cfg(not(target_feature = "avx2"))]
164                compile_error!("You must enable `avx2` target feature with \
165                    `chacha20_force_avx2` configuration option");
166                type Tokens = ();
167            } else if #[cfg(chacha20_force_sse2)] {
168                #[cfg(not(target_feature = "sse2"))]
169                compile_error!("You must enable `sse2` target feature with \
170                    `chacha20_force_sse2` configuration option");
171                type Tokens = ();
172            } else {
173                cpufeatures::new!(avx2_cpuid, "avx2");
174                cpufeatures::new!(sse2_cpuid, "sse2");
175                type Tokens = (avx2_cpuid::InitToken, sse2_cpuid::InitToken);
176            }
177        }
178    } else {
179        type Tokens = ();
180    }
181}
182
183/// The ChaCha core function.
184pub struct ChaChaCore<R: Unsigned> {
185    /// Internal state of the core function
186    state: [u32; STATE_WORDS],
187    /// CPU target feature tokens
188    #[allow(dead_code)]
189    tokens: Tokens,
190    /// Number of rounds to perform
191    rounds: PhantomData<R>,
192}
193
194impl<R: Unsigned> KeySizeUser for ChaChaCore<R> {
195    type KeySize = U32;
196}
197
198impl<R: Unsigned> IvSizeUser for ChaChaCore<R> {
199    type IvSize = U12;
200}
201
202impl<R: Unsigned> BlockSizeUser for ChaChaCore<R> {
203    type BlockSize = U64;
204}
205
206impl<R: Unsigned> KeyIvInit for ChaChaCore<R> {
207    #[inline]
208    fn new(key: &Key, iv: &Nonce) -> Self {
209        let mut state = [0u32; STATE_WORDS];
210        state[0..4].copy_from_slice(&CONSTANTS);
211        let key_chunks = key.chunks_exact(4);
212        for (val, chunk) in state[4..12].iter_mut().zip(key_chunks) {
213            *val = u32::from_le_bytes(chunk.try_into().unwrap());
214        }
215        let iv_chunks = iv.chunks_exact(4);
216        for (val, chunk) in state[13..16].iter_mut().zip(iv_chunks) {
217            *val = u32::from_le_bytes(chunk.try_into().unwrap());
218        }
219
220        cfg_if! {
221            if #[cfg(chacha20_force_soft)] {
222                let tokens = ();
223            } else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
224                cfg_if! {
225                    if #[cfg(chacha20_force_avx2)] {
226                        let tokens = ();
227                    } else if #[cfg(chacha20_force_sse2)] {
228                        let tokens = ();
229                    } else {
230                        let tokens = (avx2_cpuid::init(), sse2_cpuid::init());
231                    }
232                }
233            } else {
234                let tokens = ();
235            }
236        }
237
238        Self {
239            state,
240            tokens,
241            rounds: PhantomData,
242        }
243    }
244}
245
246impl<R: Unsigned> StreamCipherCore for ChaChaCore<R> {
247    #[inline(always)]
248    fn remaining_blocks(&self) -> Option<usize> {
249        let rem = u32::MAX - self.get_block_pos();
250        rem.try_into().ok()
251    }
252
253    fn process_with_backend(&mut self, f: impl StreamClosure<BlockSize = Self::BlockSize>) {
254        cfg_if! {
255            if #[cfg(chacha20_force_soft)] {
256                f.call(&mut backends::soft::Backend(self));
257            } else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
258                cfg_if! {
259                    if #[cfg(chacha20_force_avx2)] {
260                        unsafe {
261                            backends::avx2::inner::<R, _>(&mut self.state, f);
262                        }
263                    } else if #[cfg(chacha20_force_sse2)] {
264                        unsafe {
265                            backends::sse2::inner::<R, _>(&mut self.state, f);
266                        }
267                    } else {
268                        let (avx2_token, sse2_token) = self.tokens;
269                        if avx2_token.get() {
270                            unsafe {
271                                backends::avx2::inner::<R, _>(&mut self.state, f);
272                            }
273                        } else if sse2_token.get() {
274                            unsafe {
275                                backends::sse2::inner::<R, _>(&mut self.state, f);
276                            }
277                        } else {
278                            f.call(&mut backends::soft::Backend(self));
279                        }
280                    }
281                }
282            } else if #[cfg(all(chacha20_force_neon, target_arch = "aarch64", target_feature = "neon"))] {
283                unsafe {
284                    backends::neon::inner::<R, _>(&mut self.state, f);
285                }
286            } else {
287                f.call(&mut backends::soft::Backend(self));
288            }
289        }
290    }
291}
292
293impl<R: Unsigned> StreamCipherSeekCore for ChaChaCore<R> {
294    type Counter = u32;
295
296    #[inline(always)]
297    fn get_block_pos(&self) -> u32 {
298        self.state[12]
299    }
300
301    #[inline(always)]
302    fn set_block_pos(&mut self, pos: u32) {
303        self.state[12] = pos;
304    }
305}
306
307#[cfg(feature = "zeroize")]
308#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))]
309impl<R: Unsigned> Drop for ChaChaCore<R> {
310    fn drop(&mut self) {
311        self.state.zeroize();
312    }
313}
314
315#[cfg(feature = "zeroize")]
316#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))]
317impl<R: Unsigned> ZeroizeOnDrop for ChaChaCore<R> {}