zeros/
chacha.rs

1/*
2==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--
3
4Zeros
5
6Copyright (C) 2019-2025  Anonymous
7
8There are several releases over multiple years,
9they are listed as ranges, such as: "2019-2025".
10
11This program is free software: you can redistribute it and/or modify
12it under the terms of the GNU Lesser General Public License as published by
13the Free Software Foundation, either version 3 of the License, or
14(at your option) any later version.
15
16This program is distributed in the hope that it will be useful,
17but WITHOUT ANY WARRANTY; without even the implied warranty of
18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19GNU Lesser General Public License for more details.
20
21You should have received a copy of the GNU Lesser General Public License
22along with this program.  If not, see <https://www.gnu.org/licenses/>.
23
24::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--
25*/
26
27//! # Chacha
28//!
29//! _Implemetation of [Chacha][site:chacha]._
30//!
31//! ## Notes
32//!
33//! - [Keys][type:Key] must be 256-bit, [nonces][type:Nonce] must be 128-bit.
34//! - [`Write::write()`][fn:std/io/Write#write] implemetation of [`Chacha`][struct:Chacha]: see [`Chacha::encrypt()`][fn:Chacha#encrypt] for
35//!   details.
36//!
37//! - Encrypting and decrypting take same steps:
38//!
39//!     + To encrypt, make new `Chacha` instance and write data into it.
40//!     + To decrypt, make new `Chacha` instance and write (encrypted) data into it.
41//!
42//! - This implementation has been tested against [`openssl` crate][crate:openssl] (which uses [OpenSSL][site:OpenSSL] underneath).
43//!
44//! ## Examples
45//!
46//! Below is an example of encrypting/decrypting. You can use it for large data. For small amount of data, see [`Variant`][enum:Variant] for
47//! some shortcuts.
48//!
49//! ```
50//! use zeros::chacha::{self, Key, Variant};
51//!
52//! const KEY: Key = *b"00000000001111111111222222222233";
53//! const NONCE: chacha::Nonce = *b"0123456789abcdef";
54//! const DATA: &[u8] = b"test";
55//!
56//! let new_chacha20 = || Variant::Twenty.new(KEY, NONCE, Vec::with_capacity(DATA.len()));
57//! let encrypted = {
58//!     let mut chacha20 = new_chacha20();
59//!     chacha20.encrypt(DATA)?;
60//!     chacha20.finish()?
61//! };
62//! assert_eq!(DATA.len(), encrypted.len());
63//! assert_ne!(DATA, encrypted);
64//!
65//! // Decrypting
66//! assert_eq!(DATA, {
67//!     let mut chacha20 = new_chacha20();
68//!     chacha20.encrypt(encrypted)?;
69//!     chacha20.finish()?
70//! });
71//!
72//! # zeros::Result::Ok(())
73//! ```
74//!
75//! ## References:
76//!
77//! - <https://cr.yp.to/chacha.html>
78//! - <https://cr.yp.to/streamciphers/timings/estreambench/submissions/salsa20/chacha20/ref/chacha.c>
79//! - <https://cr.yp.to/streamciphers/timings/estreambench/submissions/salsa20/chacha20/ref/ecrypt-sync.h>
80//! - <https://cr.yp.to/snuffle/ecrypt-config.h>
81//! - <https://cr.yp.to/snuffle/ecrypt-portable.h>
82//!
83//! [site:chacha]: https://cr.yp.to/chacha.html
84//! [site:OpenSSL]: https://www.openssl.org
85//!
86//! [crate:openssl]: https://crates.io/crates/openssl
87//!
88//! [enum:Variant]: enum.Variant.html
89//! [struct:Chacha]: struct.Chacha.html
90//! [type:Key]: type.Key.html
91//! [type:Nonce]: type.Nonce.html
92//! [fn:Chacha#encrypt]: struct.Chacha.html#method.encrypt
93//! [fn:Chacha#finish]: struct.Chacha.html#method.finish
94//!
95//! [fn:std/io/Write#write]: https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.write
96
97#![cfg(target_endian="little")]
98#![doc(cfg(target_endian="little"))]
99
100use {
101    alloc::vec::Vec,
102    crate::{Bytes, Result},
103    self::word_array::WordArray,
104};
105
106#[cfg(not(feature="std"))]
107use crate::io::Write;
108
109#[cfg(feature="std")]
110use {
111    std::io::Write,
112    crate::IoResult,
113};
114
115#[cfg(feature="simd")]
116use core::simd::Simd;
117
118#[cfg(test)]
119use {
120    core::mem,
121    crate::keccak::Hash,
122    openssl::symm::{Cipher, Crypter, Mode},
123};
124
125#[cfg(test)]
126#[cfg(feature="std")]
127use std::thread;
128
129#[cfg(test)]
130mod tests;
131
132mod variant;
133
134pub use self::variant::*;
135
136#[cfg(not(feature="simd"))]
137#[doc(cfg(not(feature="simd")))]
138pub (crate) mod salsa20;
139
140#[cfg(feature="simd")]
141#[doc(cfg(feature="simd"))]
142pub (crate) mod salsa20_simd;
143
144pub (crate) mod word_array;
145
146/// # Key (`256` bits)
147pub type Key = [u8; KEY_SIZE_IN_BYTES];
148
149/// # Key size in bytes
150const KEY_SIZE_IN_BYTES: usize = 32;
151
152/// # Nonce (`128` bits)
153///
154/// ## Notes
155///
156/// This is __`128-bit`__, which is different to [`poly1305::chacha20::Nonce`][type:poly1305/chacha20/Nonce] (`96` bits), or
157/// [`poly1305::xchacha20::Nonce`][type:poly1305/xchacha20/Nonce] (`192` bits).
158///
159/// [type:poly1305/chacha20/Nonce]: ../poly1305/chacha20/type.Nonce.html
160/// [type:poly1305/xchacha20/Nonce]: ../poly1305/xchacha20/type.Nonce.html
161pub type Nonce = [u8; NONCE_SIZE_IN_BYTES];
162
163/// # Nonce size in bytes
164const NONCE_SIZE_IN_BYTES: usize = 16;
165
166/// # Block size in bytes
167pub (crate) const BLOCK_SIZE_IN_BYTES: usize = 64;
168
169/// # Block
170pub (crate) type Block = [u8; BLOCK_SIZE_IN_BYTES];
171
172#[cfg(any(feature="simd", test))]
173const BLOCK_OF_ZEROS: Block = [0; BLOCK_SIZE_IN_BYTES];
174
175#[cfg(test)]
176const KEY_HASH: Hash = Hash::Sha3_256;
177#[cfg(test)]
178const NONCE_HASH: Hash = Hash::Shake128;
179
180/// # Chacha20
181pub (crate) const TWENTY: Variant = Variant::Twenty;
182
183/// # Chacha
184///
185/// You can make new instance via [`Variant`][enum:Variant].
186///
187/// [enum:Variant]: enum.Variant.html
188#[derive(Debug)]
189pub struct Chacha<W> where W: Write {
190    variant: Variant,
191    data: WordArray,
192    buffer: Vec<u8>,
193    output: W,
194}
195
196impl<W> Chacha<W> where W: Write {
197
198    /// # Gets variant
199    pub const fn variant(&self) -> &Variant {
200        &self.variant
201    }
202
203    /// # Encrypts new bytes
204    ///
205    /// This function always returns your data's size, but it does *NOT* always write your whole data out. It's because encrypting works only on
206    /// blocks of a fixed size. Exceeded data will be kept in an internal buffer. That buffer will be processed in
207    /// [`finish()`](#method.finish).
208    #[inline]
209    pub fn encrypt<B>(&mut self, bytes: B) -> Result<usize> where B: AsRef<[u8]> {
210        let mut bytes = bytes.as_ref();
211
212        let result = bytes.len();
213        if result == 0 {
214            return Ok(result);
215        }
216
217        // Process buffer first
218        if crate::io::fill_buffer(&mut self.buffer, BLOCK_SIZE_IN_BYTES, &mut bytes) {
219            self.process_buffer_and_write_to_output(None)?;
220            self.buffer.clear();
221        }
222
223        // Now the bytes
224        {
225            // Docs say chunks_exact() can be optimized better than chunks()
226            let chunks = bytes.chunks_exact(BLOCK_SIZE_IN_BYTES);
227            self.buffer.extend(chunks.remainder());
228            for c in chunks {
229                self.process_buffer_and_write_to_output(Some(c))?;
230            }
231        }
232
233        Ok(result)
234    }
235
236    /// # Encrypts new data
237    ///
238    /// Returns length of input bytes. If overflow happens, `None` will be returned.
239    ///
240    /// See [`encrypt()`](#method.encrypt) for more details.
241    pub fn encrypt_bytes<'a, const N: usize, B, B0>(&mut self, bytes: B) -> Result<Option<usize>>
242    where B: Into<Bytes<'a, N, B0>>, B0: AsRef<[u8]> + 'a {
243        let mut result = Some(usize::MIN);
244        for bytes in bytes.into().as_slice() {
245            let size = self.encrypt(bytes)?;
246            if let Some(current) = result.as_mut() {
247                match current.checked_add(size) {
248                    Some(new) => *current = new,
249                    None => result = None,
250                };
251            }
252        }
253
254        Ok(result)
255    }
256
257    /// # Processes buffer and writes to output
258    ///
259    /// - If buffer is `None`, self buffer will be used.
260    /// - Buffer's size must be less than or equal to `BLOCK_SIZE_IN_BYTES`. Or an error will be returned.
261    /// - Only the final buffer can have size less than `BLOCK_SIZE_IN_BYTES`. Or the encrypted data will be **BROKEN**. This is
262    ///   responsibility of the caller (you).
263    #[inline]
264    fn process_buffer_and_write_to_output(&mut self, buffer: Option<&[u8]>) -> Result<()> {
265        let buffer = buffer.unwrap_or(&self.buffer);
266
267        let buffer_len = match buffer.len() {
268            0 => return Ok(()),
269            buffer_len @ 1..=BLOCK_SIZE_IN_BYTES => buffer_len,
270            other => return Err(err!("Buffer is too large: {other} (max allowed: {max})", other=other, max=BLOCK_SIZE_IN_BYTES)),
271        };
272
273        #[cfg(feature="simd")]
274        let mut output = salsa20_simd::words_to_bytes(&self.variant, &self.data);
275        #[cfg(not(feature="simd"))]
276        let mut output = salsa20::words_to_bytes(&self.variant, &self.data);
277        {
278            const TWELFTH: usize = 12;
279            self.data[TWELFTH] = self.data[TWELFTH].wrapping_add(1);
280            if self.data[TWELFTH] == 0 {
281                const THIRTEENTH: usize = TWELFTH + 1;
282                self.data[THIRTEENTH] = self.data[THIRTEENTH].wrapping_add(1);
283            }
284        }
285
286        #[cfg(feature="simd")] {
287            output = {
288                let mut tmp = BLOCK_OF_ZEROS;
289                tmp[..buffer_len].copy_from_slice(buffer);
290                (Simd::from_array(output) ^ Simd::from_array(tmp)).to_array()
291            };
292        }
293        #[cfg(not(feature="simd"))]
294        (0..buffer_len).for_each(|i| output[i] ^= buffer[i]);
295
296        let result = self.output.write_all(&output[..buffer_len]);
297        #[cfg(feature="std")]
298        let result = result.map_err(|e| from_io_err!(e));
299        result
300    }
301
302    /// # Mutable Output
303    #[cfg(feature="std")]
304    #[inline(always)]
305    pub (crate) const fn mut_output(&mut self) -> &mut W {
306        &mut self.output
307    }
308
309    /// # Finishes and returns your output
310    #[must_use]
311    pub fn finish(mut self) -> Result<W> {
312        self.process_buffer_and_write_to_output(None)?;
313        drop(self.buffer);
314
315        #[cfg(feature="std")]
316        from_io_err!(self.output.flush())?;
317
318        Ok(self.output)
319    }
320
321}
322
323#[cfg(feature="std")]
324#[doc(cfg(feature="std"))]
325impl<W> Write for Chacha<W> where W: Write {
326
327    fn write(&mut self, buffer: &[u8]) -> IoResult<usize> {
328        Ok(self.encrypt(buffer)?)
329    }
330
331    fn flush(&mut self) -> IoResult<()> {
332        self.output.flush()
333    }
334
335}
336
337#[test]
338fn tests() {
339    assert_eq!(mem::size_of::<Block>(), mem::size_of::<WordArray>());
340
341    assert_eq!(KEY_SIZE_IN_BYTES, 32);
342    assert_eq!(mem::size_of::<Key>(), KEY_SIZE_IN_BYTES);
343
344    assert_eq!(NONCE_SIZE_IN_BYTES, 16);
345    assert_eq!(mem::size_of::<Nonce>(), NONCE_SIZE_IN_BYTES);
346}
347
348/// # Encrypts using OpenSSL's Chacha20
349#[cfg(test)]
350fn encrypt_using_open_ssl_chacha20<K, N, B>(mode: Mode, key: K, nonce: N, bytes: B) -> Vec<u8>
351where K: AsRef<[u8]>, N: AsRef<[u8]>, B: AsRef<[u8]> {
352    let bytes = bytes.as_ref();
353
354    let mut crypter = Crypter::new(Cipher::chacha20(), mode, key.as_ref(), Some(nonce.as_ref())).unwrap();
355    let mut result = Vec::with_capacity(bytes.len());
356
357    let mut output = BLOCK_OF_ZEROS;
358    for c in bytes.chunks(BLOCK_SIZE_IN_BYTES) {
359        let count = crypter.update(c, &mut output).unwrap();
360        result.extend(&output[..count]);
361    }
362    {
363        let count = crypter.finalize(&mut output).unwrap();
364        result.extend(&output[..count]);
365    }
366
367    result
368}
369
370/// OpenSSL says that for nonces:
371///
372/// - The first 4 bytes is a counter.
373/// - The last 12 bytes is nonce.
374///
375/// (Original C implementation does not mention counter anywhere. It mentions 'nonce' once, but vaguely.)
376///
377/// See EVP_chacha20(3ossl).
378#[test]
379fn cmp_to_data_generated_by_open_ssl() -> Result<()> {
380    const DATA: &[u8] = &[
381        0x50, 0xa6, 0xb7, 0xec, 0xb4, 0x2c, 0xc8, 0x9a, 0xbd, 0x5c, 0x40, 0xea, 0x5e, 0x30, 0x66, 0x31, 0xbf, 0x93, 0x49, 0x6a, 0xc5, 0x01,
382        0x56, 0xb3, 0x6f, 0x51, 0x56, 0xe8, 0x56, 0x89, 0xe8, 0x56, 0x08, 0xe5, 0xb0, 0xe6, 0xa0, 0xd9, 0x3c, 0x1c, 0x6a, 0x8a, 0xd7, 0x12,
383        0x09, 0xf0, 0xac, 0x9d, 0x57, 0xb0, 0x45, 0x33, 0x9d, 0x1f, 0x60, 0x1d, 0x34, 0xf8, 0xa7, 0xa0, 0x4e, 0x42, 0x64, 0xae,
384        0x98, 0xda, 0xad, 0x4a, 0x94, 0x82, 0xcc, 0x9b, 0x84, 0x82, 0x1f, 0x50, 0x4f, 0xc0, 0x44, 0xba,
385    ];
386
387    let key = &KEY_HASH.hash("key");
388    let nonce = &NONCE_HASH.hash("nonce");
389    let encrypt = |data| TWENTY.encrypt(key, nonce, data);
390    let encrypt_using_open_ssl_chacha20 = |mode, data| encrypt_using_open_ssl_chacha20(mode, key, nonce, data);
391
392    let encrypted = encrypt(DATA)?;
393    assert_eq!(DATA.len(), encrypted.len());
394    assert_ne!(DATA, encrypted);
395    assert_eq!(encrypted, encrypt_using_open_ssl_chacha20(Mode::Encrypt, DATA));
396
397    assert_eq!(DATA, encrypt(&encrypted)?);
398    assert_eq!(DATA, encrypt_using_open_ssl_chacha20(Mode::Decrypt, &encrypted));
399
400    Ok(())
401}
402
403#[test]
404#[cfg(feature="std")]
405fn cmp_to_data_generated_by_open_ssl_using_threads() -> Result<()> {
406    const INPUT_DATA: &[u8] = &[u8::MIN; BLOCK_SIZE_IN_BYTES + 1];
407
408    (-1_i16..=999).map(|index| thread::spawn(move || {
409        let key = &if index >= 0 {
410            KEY_HASH.hash(index.to_be_bytes())
411        } else {
412            // Tests for overflowing additions in `salsa20::words_to_bytes()`
413            alloc::vec![u8::MAX; KEY_SIZE_IN_BYTES]
414        };
415        let nonce = &if index % 2 == 0 {
416            alloc::vec![index as u8; NONCE_SIZE_IN_BYTES]
417        } else {
418            NONCE_HASH.hash(index.to_le_bytes())
419        };
420        assert_ne!(key, nonce);
421
422        let encrypt = |data| TWENTY.encrypt(key, nonce, data);
423        let encrypt_using_open_ssl_chacha20 = |mode, data| encrypt_using_open_ssl_chacha20(mode, key, nonce, data);
424
425        // Encrypt
426        let encrypted_data = encrypt(INPUT_DATA)?;
427        assert_eq!(INPUT_DATA.len(), encrypted_data.len());
428        assert_eq!(encrypted_data, encrypt_using_open_ssl_chacha20(Mode::Encrypt, INPUT_DATA));
429
430        // Decrypt
431        assert_eq!(INPUT_DATA, encrypt(&encrypted_data)?);
432        assert_eq!(INPUT_DATA, encrypt_using_open_ssl_chacha20(Mode::Decrypt, &encrypted_data));
433
434        Result::Ok(())
435    })).collect::<Vec<_>>().into_iter().for_each(|t| t.join().unwrap().unwrap());
436
437    Ok(())
438}