salsa20/
lib.rs

1//! Implementation of the [Salsa] family of stream ciphers.
2//!
3//! Cipher functionality is accessed using traits from re-exported [`cipher`] crate.
4//!
5//! # ⚠️ Security Warning: Hazmat!
6//!
7//! This crate does not ensure ciphertexts are authentic! Thus ciphertext integrity
8//! is not verified, which can lead to serious vulnerabilities!
9//!
10//! USE AT YOUR OWN RISK!
11//!
12//! # Diagram
13//!
14//! This diagram illustrates the Salsa quarter round function.
15//! Each round consists of four quarter-rounds:
16//!
17//! <img src="https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/img/stream-ciphers/salsa20.png" width="300px">
18//!
19//! Legend:
20//!
21//! - ⊞ add
22//! - ‹‹‹ rotate
23//! - ⊕ xor
24//!
25//! # Example
26//! ```
27//! use salsa20::Salsa20;
28//! // Import relevant traits
29//! use salsa20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
30//! use hex_literal::hex;
31//!
32//! let key = [0x42; 32];
33//! let nonce = [0x24; 8];
34//! let plaintext = hex!("00010203 04050607 08090A0B 0C0D0E0F");
35//! let ciphertext = hex!("85843cc5 d58cce7b 5dd3dd04 fa005ded");
36//!
37//! // Key and IV must be references to the `GenericArray` type.
38//! // Here we use the `Into` trait to convert arrays into it.
39//! let mut cipher = Salsa20::new(&key.into(), &nonce.into());
40//!
41//! let mut buffer = plaintext.clone();
42//!
43//! // apply keystream (encrypt)
44//! cipher.apply_keystream(&mut buffer);
45//! assert_eq!(buffer, ciphertext);
46//!
47//! let ciphertext = buffer.clone();
48//!
49//! // Salsa ciphers support seeking
50//! cipher.seek(0u32);
51//!
52//! // decrypt ciphertext by applying keystream again
53//! cipher.apply_keystream(&mut buffer);
54//! assert_eq!(buffer, plaintext);
55//!
56//! // stream ciphers can be used with streaming messages
57//! cipher.seek(0u32);
58//! for chunk in buffer.chunks_mut(3) {
59//!     cipher.apply_keystream(chunk);
60//! }
61//! assert_eq!(buffer, ciphertext);
62//! ```
63//!
64//! [Salsa]: https://en.wikipedia.org/wiki/Salsa20
65
66#![no_std]
67#![cfg_attr(docsrs, feature(doc_cfg))]
68#![doc(
69    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
70    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
71    html_root_url = "https://docs.rs/salsa20/0.10.2"
72)]
73#![forbid(unsafe_code)]
74#![warn(missing_docs, rust_2018_idioms, trivial_casts, unused_qualifications)]
75
76pub use cipher;
77
78use cipher::{
79    consts::{U1, U10, U24, U32, U4, U6, U64, U8},
80    generic_array::{typenum::Unsigned, GenericArray},
81    Block, BlockSizeUser, IvSizeUser, KeyIvInit, KeySizeUser, ParBlocksSizeUser, StreamBackend,
82    StreamCipherCore, StreamCipherCoreWrapper, StreamCipherSeekCore, StreamClosure,
83};
84use core::marker::PhantomData;
85
86#[cfg(feature = "zeroize")]
87use cipher::zeroize::{Zeroize, ZeroizeOnDrop};
88
89mod xsalsa;
90
91pub use xsalsa::{hsalsa, XSalsa12, XSalsa20, XSalsa8, XSalsaCore};
92
93/// Salsa20/8 stream cipher
94/// (reduced-round variant of Salsa20 with 8 rounds, *not recommended*)
95pub type Salsa8 = StreamCipherCoreWrapper<SalsaCore<U4>>;
96
97/// Salsa20/12 stream cipher
98/// (reduced-round variant of Salsa20 with 12 rounds, *not recommended*)
99pub type Salsa12 = StreamCipherCoreWrapper<SalsaCore<U6>>;
100
101/// Salsa20/20 stream cipher
102/// (20 rounds; **recommended**)
103pub type Salsa20 = StreamCipherCoreWrapper<SalsaCore<U10>>;
104
105/// Key type used by all Salsa variants and [`XSalsa20`].
106pub type Key = GenericArray<u8, U32>;
107
108/// Nonce type used by all Salsa variants.
109pub type Nonce = GenericArray<u8, U8>;
110
111/// Nonce type used by [`XSalsa20`].
112pub type XNonce = GenericArray<u8, U24>;
113
114/// Number of 32-bit words in the Salsa20 state
115const STATE_WORDS: usize = 16;
116
117/// State initialization constant ("expand 32-byte k")
118const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];
119
120/// The Salsa20 core function.
121pub struct SalsaCore<R: Unsigned> {
122    /// Internal state of the core function
123    state: [u32; STATE_WORDS],
124    /// Number of rounds to perform
125    rounds: PhantomData<R>,
126}
127
128impl<R: Unsigned> SalsaCore<R> {
129    /// Create new Salsa core from raw state.
130    ///
131    /// This method is mainly intended for the `scrypt` crate.
132    /// Other users generally should not use this method.
133    pub fn from_raw_state(state: [u32; STATE_WORDS]) -> Self {
134        Self {
135            state,
136            rounds: PhantomData,
137        }
138    }
139}
140
141impl<R: Unsigned> KeySizeUser for SalsaCore<R> {
142    type KeySize = U32;
143}
144
145impl<R: Unsigned> IvSizeUser for SalsaCore<R> {
146    type IvSize = U8;
147}
148
149impl<R: Unsigned> BlockSizeUser for SalsaCore<R> {
150    type BlockSize = U64;
151}
152
153impl<R: Unsigned> KeyIvInit for SalsaCore<R> {
154    fn new(key: &Key, iv: &Nonce) -> Self {
155        let mut state = [0u32; STATE_WORDS];
156        state[0] = CONSTANTS[0];
157
158        for (i, chunk) in key[..16].chunks(4).enumerate() {
159            state[1 + i] = u32::from_le_bytes(chunk.try_into().unwrap());
160        }
161
162        state[5] = CONSTANTS[1];
163
164        for (i, chunk) in iv.chunks(4).enumerate() {
165            state[6 + i] = u32::from_le_bytes(chunk.try_into().unwrap());
166        }
167
168        state[8] = 0;
169        state[9] = 0;
170        state[10] = CONSTANTS[2];
171
172        for (i, chunk) in key[16..].chunks(4).enumerate() {
173            state[11 + i] = u32::from_le_bytes(chunk.try_into().unwrap());
174        }
175
176        state[15] = CONSTANTS[3];
177
178        Self {
179            state,
180            rounds: PhantomData,
181        }
182    }
183}
184
185impl<R: Unsigned> StreamCipherCore for SalsaCore<R> {
186    #[inline(always)]
187    fn remaining_blocks(&self) -> Option<usize> {
188        let rem = u64::MAX - self.get_block_pos();
189        rem.try_into().ok()
190    }
191    fn process_with_backend(&mut self, f: impl StreamClosure<BlockSize = Self::BlockSize>) {
192        f.call(&mut Backend(self));
193    }
194}
195
196impl<R: Unsigned> StreamCipherSeekCore for SalsaCore<R> {
197    type Counter = u64;
198
199    #[inline(always)]
200    fn get_block_pos(&self) -> u64 {
201        (self.state[8] as u64) + ((self.state[9] as u64) << 32)
202    }
203
204    #[inline(always)]
205    fn set_block_pos(&mut self, pos: u64) {
206        self.state[8] = (pos & 0xffff_ffff) as u32;
207        self.state[9] = ((pos >> 32) & 0xffff_ffff) as u32;
208    }
209}
210
211#[cfg(feature = "zeroize")]
212#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))]
213impl<R: Unsigned> Drop for SalsaCore<R> {
214    fn drop(&mut self) {
215        self.state.zeroize();
216    }
217}
218
219#[cfg(feature = "zeroize")]
220#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))]
221impl<R: Unsigned> ZeroizeOnDrop for SalsaCore<R> {}
222
223struct Backend<'a, R: Unsigned>(&'a mut SalsaCore<R>);
224
225impl<'a, R: Unsigned> BlockSizeUser for Backend<'a, R> {
226    type BlockSize = U64;
227}
228
229impl<'a, R: Unsigned> ParBlocksSizeUser for Backend<'a, R> {
230    type ParBlocksSize = U1;
231}
232
233impl<'a, R: Unsigned> StreamBackend for Backend<'a, R> {
234    #[inline(always)]
235    fn gen_ks_block(&mut self, block: &mut Block<Self>) {
236        let res = run_rounds::<R>(&self.0.state);
237        self.0.set_block_pos(self.0.get_block_pos() + 1);
238
239        for (chunk, val) in block.chunks_exact_mut(4).zip(res.iter()) {
240            chunk.copy_from_slice(&val.to_le_bytes());
241        }
242    }
243}
244
245#[inline]
246#[allow(clippy::many_single_char_names)]
247pub(crate) fn quarter_round(
248    a: usize,
249    b: usize,
250    c: usize,
251    d: usize,
252    state: &mut [u32; STATE_WORDS],
253) {
254    state[b] ^= state[a].wrapping_add(state[d]).rotate_left(7);
255    state[c] ^= state[b].wrapping_add(state[a]).rotate_left(9);
256    state[d] ^= state[c].wrapping_add(state[b]).rotate_left(13);
257    state[a] ^= state[d].wrapping_add(state[c]).rotate_left(18);
258}
259
260#[inline(always)]
261fn run_rounds<R: Unsigned>(state: &[u32; STATE_WORDS]) -> [u32; STATE_WORDS] {
262    let mut res = *state;
263
264    for _ in 0..R::USIZE {
265        // column rounds
266        quarter_round(0, 4, 8, 12, &mut res);
267        quarter_round(5, 9, 13, 1, &mut res);
268        quarter_round(10, 14, 2, 6, &mut res);
269        quarter_round(15, 3, 7, 11, &mut res);
270
271        // diagonal rounds
272        quarter_round(0, 1, 2, 3, &mut res);
273        quarter_round(5, 6, 7, 4, &mut res);
274        quarter_round(10, 11, 8, 9, &mut res);
275        quarter_round(15, 12, 13, 14, &mut res);
276    }
277
278    for (s1, s0) in res.iter_mut().zip(state.iter()) {
279        *s1 = s1.wrapping_add(*s0);
280    }
281    res
282}