crypto_secretbox/
lib.rs

1#![no_std]
2#![cfg_attr(docsrs, feature(doc_auto_cfg))]
3#![doc = include_str!("../README.md")]
4#![doc(
5    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
6    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
7)]
8#![forbid(unsafe_code)]
9#![warn(missing_docs, rust_2018_idioms)]
10
11//! # Usage
12//!
13#![cfg_attr(all(feature = "getrandom", feature = "std"), doc = "```")]
14#![cfg_attr(not(all(feature = "getrandom", feature = "std")), doc = "```ignore")]
15//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
16//! use crypto_secretbox::{
17//!     aead::{Aead, AeadCore, KeyInit, OsRng},
18//!     XSalsa20Poly1305, Nonce
19//! };
20//!
21//! let key = XSalsa20Poly1305::generate_key(&mut OsRng);
22//! let cipher = XSalsa20Poly1305::new(&key);
23//! let nonce = XSalsa20Poly1305::generate_nonce(&mut OsRng); // unique per message
24//! let ciphertext = cipher.encrypt(&nonce, b"plaintext message".as_ref())?;
25//! let plaintext = cipher.decrypt(&nonce, ciphertext.as_ref())?;
26//! assert_eq!(&plaintext, b"plaintext message");
27//! # Ok(())
28//! # }
29//! ```
30//!
31//! ## In-place Usage (eliminates `alloc` requirement)
32//!
33//! This crate has an optional `alloc` feature which can be disabled in e.g.
34//! microcontroller environments that don't have a heap.
35//!
36//! The [`AeadInPlace::encrypt_in_place`] and [`AeadInPlace::decrypt_in_place`]
37//! methods accept any type that impls the [`aead::Buffer`] trait which
38//! contains the plaintext for encryption or ciphertext for decryption.
39//!
40//! Note that if you enable the `heapless` feature of this crate,
41//! you will receive an impl of [`aead::Buffer`] for `heapless::Vec`
42//! (re-exported from the `aead` crate as [`aead::heapless::Vec`]),
43//! which can then be passed as the `buffer` parameter to the in-place encrypt
44//! and decrypt methods:
45//!
46#![cfg_attr(
47    all(feature = "getrandom", feature = "heapless", feature = "std"),
48    doc = "```"
49)]
50#![cfg_attr(
51    not(all(feature = "getrandom", feature = "heapless", feature = "std")),
52    doc = "```ignore"
53)]
54//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
55//! use crypto_secretbox::{
56//!     aead::{AeadCore, AeadInPlace, KeyInit, OsRng, heapless::Vec},
57//!     XSalsa20Poly1305, Nonce,
58//! };
59//!
60//! let key = XSalsa20Poly1305::generate_key(&mut OsRng);
61//! let cipher = XSalsa20Poly1305::new(&key);
62//! let nonce = XSalsa20Poly1305::generate_nonce(&mut OsRng); // unique per message
63//!
64//! let mut buffer: Vec<u8, 128> = Vec::new(); // Note: buffer needs 16-bytes overhead for auth tag
65//! buffer.extend_from_slice(b"plaintext message");
66//!
67//! // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext
68//! cipher.encrypt_in_place(&nonce, b"", &mut buffer)?;
69//!
70//! // `buffer` now contains the message ciphertext
71//! assert_ne!(&buffer, b"plaintext message");
72//!
73//! // Decrypt `buffer` in-place, replacing its ciphertext context with the original plaintext
74//! cipher.decrypt_in_place(&nonce, b"", &mut buffer)?;
75//! assert_eq!(&buffer, b"plaintext message");
76//! # Ok(())
77//! # }
78//! ```
79//!
80//! ```
81//! # #[cfg(feature = "heapless")]
82//! # {
83//! use crypto_secretbox::XSalsa20Poly1305;
84//! use crypto_secretbox::aead::{AeadCore, AeadInPlace, KeyInit, generic_array::GenericArray};
85//! use crypto_secretbox::aead::heapless::Vec;
86//!
87//! let key = GenericArray::from_slice(b"an example very very secret key.");
88//! let cipher = XSalsa20Poly1305::new(key);
89//!
90//! let nonce = GenericArray::from_slice(b"extra long unique nonce!"); // 24-bytes; unique
91//!
92//! let mut buffer: Vec<u8, 128> = Vec::new();
93//! buffer.extend_from_slice(b"plaintext message");
94//!
95//! // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext
96//! cipher.encrypt_in_place(nonce, b"", &mut buffer).expect("encryption failure!");
97//!
98//! // `buffer` now contains the message ciphertext
99//! assert_ne!(&buffer, b"plaintext message");
100//!
101//! // Decrypt `buffer` in-place, replacing its ciphertext context with the original plaintext
102//! cipher.decrypt_in_place(nonce, b"", &mut buffer).expect("decryption failure!");
103//! assert_eq!(&buffer, b"plaintext message");
104//! # }
105//! ```
106//!
107//! [1]: https://nacl.cr.yp.to/secretbox.html
108//! [2]: https://en.wikipedia.org/wiki/Authenticated_encryption
109//! [3]: https://docs.rs/salsa20
110//! [4]: http://docs.rs/chacha20poly1305
111//! [5]: https://docs.rs/chacha20poly1305/latest/chacha20poly1305/struct.XChaCha20Poly1305.html
112//! [6]: https://tools.ietf.org/html/rfc8439
113
114pub use aead::{self, consts, AeadCore, AeadInPlace, Error, KeyInit, KeySizeUser};
115pub use cipher;
116
117use aead::{
118    consts::{U0, U16, U24, U32, U8},
119    generic_array::GenericArray,
120    Buffer,
121};
122use cipher::{IvSizeUser, KeyIvInit, StreamCipher};
123use core::marker::PhantomData;
124use poly1305::Poly1305;
125use zeroize::{Zeroize, Zeroizing};
126
127#[cfg(feature = "chacha20")]
128use chacha20::{hchacha, ChaCha20Legacy as ChaCha20};
129
130#[cfg(feature = "salsa20")]
131use salsa20::{hsalsa, Salsa20};
132
133#[cfg(any(feature = "chacha20", feature = "salsa20"))]
134use cipher::consts::U10;
135
136/// Key type.
137pub type Key = GenericArray<u8, U32>;
138
139/// Nonce type.
140pub type Nonce = GenericArray<u8, U24>;
141
142/// Poly1305 tag.
143pub type Tag = GenericArray<u8, U16>;
144
145/// `crypto_secretbox` instantiated with the XChaCha20 stream cipher.
146///
147/// NOTE: this is a legacy construction which is missing modern features like
148/// additional associated data.
149///
150/// We do not recommend using it in greenfield applications, and instead only
151/// using it for interop with legacy applications which require use of the
152/// `crypto_secretbox` construction instantiated with XChaCha20.
153///
154/// For new applications, we recommend using the `AEAD_XChaCha20_Poly1305`
155/// construction as described in [`draft-irtf-cfrg-xchacha`]. An implementation
156/// of this IETF flavor of XChaCha20Poly1305 can be found in
157/// [`chacha20poly1305::XChaCha20Poly1305`].
158///
159/// [`draft-irtf-cfrg-xchacha`]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
160/// [`chacha20poly1305::XChaCha20Poly1305`]: https://docs.rs/chacha20poly1305/latest/chacha20poly1305/type.XChaCha20Poly1305.html
161#[cfg(feature = "chacha20")]
162pub type XChaCha20Poly1305 = SecretBox<ChaCha20>;
163
164/// `crypto_secretbox` instantiated with the XSalsa20 stream cipher.
165#[cfg(feature = "salsa20")]
166pub type XSalsa20Poly1305 = SecretBox<Salsa20>;
167
168/// The NaCl `crypto_secretbox` authenticated symmetric encryption primitive,
169/// generic
170pub struct SecretBox<C> {
171    /// Secret key.
172    key: Key,
173
174    /// Cipher.
175    cipher: PhantomData<C>,
176}
177
178impl<C> SecretBox<C> {
179    /// Size of an XSalsa20Poly1305 key in bytes
180    pub const KEY_SIZE: usize = 32;
181
182    /// Size of an XSalsa20Poly1305 nonce in bytes
183    pub const NONCE_SIZE: usize = 24;
184
185    /// Size of a Poly1305 tag in bytes
186    pub const TAG_SIZE: usize = 16;
187}
188
189impl<C> SecretBox<C>
190where
191    C: Kdf + KeyIvInit + KeySizeUser<KeySize = U32> + IvSizeUser<IvSize = U8> + StreamCipher,
192{
193    /// Initialize cipher instance and Poly1305 MAC.
194    fn init_cipher_and_mac(&self, nonce: &Nonce) -> (C, Poly1305) {
195        let (nonce_prefix, nonce_suffix) = nonce.split_at(16);
196        let subkey = Zeroizing::new(C::kdf(&self.key, nonce_prefix.as_ref().into()));
197        let mut cipher = C::new(&subkey, nonce_suffix.as_ref().into());
198
199        // Derive Poly1305 key from the first 32-bytes of the keystream.
200        let mut mac_key = Zeroizing::new(poly1305::Key::default());
201        cipher.apply_keystream(&mut mac_key);
202
203        let mac = Poly1305::new(&mac_key);
204        (cipher, mac)
205    }
206}
207
208// Handwritten instead of derived to avoid `C: Clone` bound
209impl<C> Clone for SecretBox<C> {
210    fn clone(&self) -> Self {
211        Self {
212            key: self.key,
213            cipher: PhantomData,
214        }
215    }
216}
217
218impl<C> KeySizeUser for SecretBox<C> {
219    type KeySize = U32;
220}
221
222impl<C> KeyInit for SecretBox<C> {
223    fn new(key: &Key) -> Self {
224        SecretBox {
225            key: *key,
226            cipher: PhantomData,
227        }
228    }
229}
230
231impl<C> AeadCore for SecretBox<C> {
232    type NonceSize = U24;
233    type TagSize = U16;
234    type CiphertextOverhead = U0;
235}
236
237impl<C> AeadInPlace for SecretBox<C>
238where
239    C: Kdf + KeyIvInit + KeySizeUser<KeySize = U32> + IvSizeUser<IvSize = U8> + StreamCipher,
240{
241    fn encrypt_in_place(
242        &self,
243        nonce: &Nonce,
244        associated_data: &[u8],
245        buffer: &mut dyn Buffer,
246    ) -> Result<(), Error> {
247        let pt_len = buffer.len();
248
249        // Make room in the buffer for the tag. It needs to be prepended.
250        buffer.extend_from_slice(Tag::default().as_slice())?;
251
252        // TODO(tarcieri): add offset param to `encrypt_in_place_detached`
253        buffer.as_mut().copy_within(..pt_len, Self::TAG_SIZE);
254
255        let tag = self.encrypt_in_place_detached(
256            nonce,
257            associated_data,
258            &mut buffer.as_mut()[Self::TAG_SIZE..],
259        )?;
260
261        buffer.as_mut()[..Self::TAG_SIZE].copy_from_slice(tag.as_slice());
262        Ok(())
263    }
264
265    fn encrypt_in_place_detached(
266        &self,
267        nonce: &Nonce,
268        associated_data: &[u8],
269        buffer: &mut [u8],
270    ) -> Result<Tag, Error> {
271        // AAD unsupported
272        if !associated_data.is_empty() {
273            return Err(Error);
274        }
275
276        let (mut cipher, mac) = self.init_cipher_and_mac(nonce);
277        cipher.apply_keystream(buffer);
278        Ok(mac.compute_unpadded(buffer))
279    }
280
281    fn decrypt_in_place(
282        &self,
283        nonce: &Nonce,
284        associated_data: &[u8],
285        buffer: &mut dyn Buffer,
286    ) -> Result<(), Error> {
287        if buffer.len() < Self::TAG_SIZE {
288            return Err(Error);
289        }
290
291        let tag = Tag::clone_from_slice(&buffer.as_ref()[..Self::TAG_SIZE]);
292
293        self.decrypt_in_place_detached(
294            nonce,
295            associated_data,
296            &mut buffer.as_mut()[Self::TAG_SIZE..],
297            &tag,
298        )?;
299
300        let pt_len = buffer.len() - Self::TAG_SIZE;
301
302        // TODO(tarcieri): add offset param to `encrypt_in_place_detached`
303        buffer.as_mut().copy_within(Self::TAG_SIZE.., 0);
304        buffer.truncate(pt_len);
305        Ok(())
306    }
307
308    fn decrypt_in_place_detached(
309        &self,
310        nonce: &Nonce,
311        associated_data: &[u8],
312        buffer: &mut [u8],
313        tag: &Tag,
314    ) -> Result<(), Error> {
315        // AAD unsupported
316        if !associated_data.is_empty() {
317            return Err(Error);
318        }
319
320        let (mut cipher, mac) = self.init_cipher_and_mac(nonce);
321        let expected_tag = mac.compute_unpadded(buffer);
322
323        // This performs a constant-time comparison using the `subtle` crate
324        use subtle::ConstantTimeEq;
325        if expected_tag.ct_eq(tag).into() {
326            cipher.apply_keystream(buffer);
327            Ok(())
328        } else {
329            Err(Error)
330        }
331    }
332}
333
334impl<C> Drop for SecretBox<C> {
335    fn drop(&mut self) {
336        self.key.as_mut_slice().zeroize();
337    }
338}
339
340/// Key derivation function: trait for abstracting over HSalsa20 and HChaCha20.
341pub trait Kdf {
342    /// Derive a new key from the provided input key and nonce.
343    fn kdf(key: &Key, nonce: &GenericArray<u8, U16>) -> Key;
344}
345
346impl<C> Kdf for SecretBox<C>
347where
348    C: Kdf,
349{
350    fn kdf(key: &Key, nonce: &GenericArray<u8, U16>) -> Key {
351        C::kdf(key, nonce)
352    }
353}
354
355#[cfg(feature = "chacha20")]
356impl Kdf for ChaCha20 {
357    fn kdf(key: &Key, nonce: &GenericArray<u8, U16>) -> Key {
358        hchacha::<U10>(key, nonce)
359    }
360}
361
362#[cfg(feature = "salsa20")]
363impl Kdf for Salsa20 {
364    fn kdf(key: &Key, nonce: &GenericArray<u8, U16>) -> Key {
365        hsalsa::<U10>(key, nonce)
366    }
367}