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> {}