chacha20poly1305/lib.rs
1#![no_std]
2#![cfg_attr(docsrs, feature(doc_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
9//! ## Supported Algorithms
10//!
11//! This crate contains pure Rust implementations of [`ChaCha20Poly1305`]
12//! (with optional AVX2 acceleration) as well as the following variants thereof:
13//!
14//! - [`XChaCha20Poly1305`] - ChaCha20Poly1305 variant with an extended 192-bit (24-byte) nonce.
15//! - [`ChaCha8Poly1305`] / [`ChaCha12Poly1305`] - non-standard, reduced-round variants
16//! (gated under the `reduced-round` Cargo feature). See the
17//! [Too Much Crypto](https://eprint.iacr.org/2019/1492.pdf)
18//! paper for background and rationale on when these constructions could be used.
19//! When in doubt, prefer [`ChaCha20Poly1305`].
20//! - [`XChaCha8Poly1305`] / [`XChaCha12Poly1305`] - same as above, but with an extended
21//! 192-bit (24-byte) nonce.
22//!
23//! # Usage
24//!
25#![cfg_attr(feature = "getrandom", doc = "```")]
26#![cfg_attr(not(feature = "getrandom"), doc = "```ignore")]
27//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
28//! // NOTE: requires the `getrandom` feature is enabled
29//!
30//! use chacha20poly1305::{
31//! aead::{Aead, AeadCore, Generate, Key, KeyInit},
32//! ChaCha20Poly1305, Nonce
33//! };
34//!
35//! let key = Key::<ChaCha20Poly1305>::generate();
36//! let cipher = ChaCha20Poly1305::new(&key);
37//!
38//! let nonce = Nonce::generate(); // MUST be unique per message
39//! let ciphertext = cipher.encrypt(&nonce, b"plaintext message".as_ref())?;
40//!
41//! let plaintext = cipher.decrypt(&nonce, ciphertext.as_ref())?;
42//! assert_eq!(&plaintext, b"plaintext message");
43//! # Ok(())
44//! # }
45//! ```
46//!
47//! ## In-place Usage (eliminates `alloc` requirement)
48//!
49//! This crate has an optional `alloc` feature which can be disabled in e.g.
50//! microcontroller environments that don't have a heap.
51//!
52//! The [`AeadInOut::encrypt_in_place`] and [`AeadInOut::decrypt_in_place`]
53//! methods accept any type that impls the [`aead::Buffer`] trait which
54//! contains the plaintext for encryption or ciphertext for decryption.
55//!
56//! Enabling the `arrayvec` feature of this crate will provide an impl of
57//! [`aead::Buffer`] for `arrayvec::ArrayVec` (re-exported from the [`aead`] crate as
58//! [`aead::arrayvec::ArrayVec`]), and enabling the `bytes` feature of this crate will
59//! provide an impl of [`aead::Buffer`] for `bytes::BytesMut` (re-exported from the
60//! [`aead`] crate as [`aead::bytes::BytesMut`]).
61//!
62//! It can then be passed as the `buffer` parameter to the in-place encrypt
63//! and decrypt methods:
64//!
65#![cfg_attr(all(feature = "getrandom", feature = "arrayvec"), doc = "```")]
66#![cfg_attr(
67 not(all(feature = "getrandom", feature = "arrayvec")),
68 doc = "```ignore"
69)]
70//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
71//! // NOTE: requires the `arrayvec` and `getrandom` features are enabled
72//!
73//! use chacha20poly1305::{
74//! aead::{AeadCore, AeadInOut, Generate, Key, KeyInit, arrayvec::ArrayVec},
75//! ChaCha20Poly1305, Nonce,
76//! };
77//!
78//! let key = Key::<ChaCha20Poly1305>::generate();
79//! let cipher = ChaCha20Poly1305::new(&key);
80//!
81//! let nonce = Nonce::generate(); // MUST be unique per message
82//! let mut buffer: ArrayVec<u8, 128> = ArrayVec::new(); // Note: buffer needs 16-bytes overhead for auth tag
83//! buffer.try_extend_from_slice(b"plaintext message").unwrap();
84//!
85//! // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext
86//! cipher.encrypt_in_place(&nonce, b"", &mut buffer)?;
87//!
88//! // `buffer` now contains the message ciphertext
89//! assert_ne!(buffer.as_ref(), b"plaintext message");
90//!
91//! // Decrypt `buffer` in-place, replacing its ciphertext context with the original plaintext
92//! cipher.decrypt_in_place(&nonce, b"", &mut buffer)?;
93//! assert_eq!(buffer.as_ref(), b"plaintext message");
94//! # Ok(())
95//! # }
96//! ```
97//!
98//! ## [`XChaCha20Poly1305`]
99//!
100//! ChaCha20Poly1305 variant with an extended 192-bit (24-byte) nonce.
101//!
102//! The construction is an adaptation of the same techniques used by
103//! XSalsa20 as described in the paper "Extending the Salsa20 Nonce"
104//! to the 96-bit nonce variant of ChaCha20, which derive a
105//! separate subkey/nonce for each extended nonce:
106//!
107//! <https://cr.yp.to/snuffle/xsalsa-20081128.pdf>
108//!
109//! No authoritative specification exists for XChaCha20Poly1305, however the
110//! construction has "rough consensus and running code" in the form of
111//! several interoperable libraries and protocols (e.g. libsodium, WireGuard)
112//! and is documented in an (expired) IETF draft, which also applies the
113//! proof from the XSalsa20 paper to the construction in order to demonstrate
114//! that XChaCha20 is secure if ChaCha20 is secure (see Section 3.1):
115//!
116//! <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha>
117//!
118//! It is worth noting that NaCl/libsodium's default "secretbox" algorithm is
119//! XSalsa20Poly1305, not XChaCha20Poly1305, and thus not compatible with
120//! this library. If you are interested in that construction, please see the
121//! `crypto_secretbox` crate:
122//!
123//! <https://docs.rs/crypto_secretbox/>
124//!
125//! # Usage
126//!
127#![cfg_attr(feature = "getrandom", doc = "```")]
128#![cfg_attr(not(feature = "getrandom"), doc = "```ignore")]
129//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
130//! // NOTE: requires the `getrandom` feature is enabled
131//!
132//! use chacha20poly1305::{
133//! aead::{Aead, AeadCore, Generate, Key, KeyInit},
134//! XChaCha20Poly1305, XNonce
135//! };
136//!
137//! let key = Key::<XChaCha20Poly1305>::generate();
138//! let cipher = XChaCha20Poly1305::new(&key);
139//! let nonce = XNonce::generate(); // MUST be unique per message
140//! let ciphertext = cipher.encrypt(&nonce, b"plaintext message".as_ref())?;
141//! let plaintext = cipher.decrypt(&nonce, ciphertext.as_ref())?;
142//! assert_eq!(&plaintext, b"plaintext message");
143//! # Ok(())
144//! # }
145//! ```
146
147mod cipher;
148
149pub use aead::{self, AeadCore, AeadInOut, Error, KeyInit, KeySizeUser, consts};
150
151use crate::cipher::Cipher;
152use ::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
153use aead::{
154 TagPosition,
155 array::{Array, ArraySize},
156 consts::{U12, U16, U24, U32},
157 inout::InOutBuf,
158};
159use chacha20::{ChaCha20, XChaCha20};
160use core::{fmt, marker::PhantomData};
161
162#[cfg(feature = "reduced-round")]
163use chacha20::{ChaCha8, ChaCha12, XChaCha8, XChaCha12};
164
165/// Key type (256-bits/32-bytes).
166///
167/// Implemented as an alias for [`Array`].
168///
169/// All [`ChaChaPoly1305`] variants (including `XChaCha20Poly1305`) use this
170/// key type.
171pub type Key = Array<u8, U32>;
172
173/// Nonce type (96-bits/12-bytes).
174///
175/// Implemented as an alias for [`Array`].
176pub type Nonce = Array<u8, U12>;
177
178/// XNonce type (192-bits/24-bytes).
179///
180/// Implemented as an alias for [`Array`].
181pub type XNonce = Array<u8, U24>;
182
183/// Poly1305 tag.
184///
185/// Implemented as an alias for [`Array`].
186pub type Tag = Array<u8, U16>;
187
188/// ChaCha20Poly1305 Authenticated Encryption with Additional Data (AEAD).
189pub type ChaCha20Poly1305 = ChaChaPoly1305<ChaCha20, U12>;
190
191/// XChaCha20Poly1305 Authenticated Encryption with Additional Data (AEAD).
192pub type XChaCha20Poly1305 = ChaChaPoly1305<XChaCha20, U24>;
193
194/// ChaCha8Poly1305 (reduced round variant) Authenticated Encryption with Additional Data (AEAD).
195#[cfg(feature = "reduced-round")]
196#[cfg_attr(docsrs, doc(cfg(feature = "reduced-round")))]
197pub type ChaCha8Poly1305 = ChaChaPoly1305<ChaCha8, U12>;
198
199/// ChaCha12Poly1305 (reduced round variant) Authenticated Encryption with Additional Data (AEAD).
200#[cfg(feature = "reduced-round")]
201#[cfg_attr(docsrs, doc(cfg(feature = "reduced-round")))]
202pub type ChaCha12Poly1305 = ChaChaPoly1305<ChaCha12, U12>;
203
204/// XChaCha8Poly1305 (reduced round variant) Authenticated Encryption with Additional Data (AEAD).
205#[cfg(feature = "reduced-round")]
206#[cfg_attr(docsrs, doc(cfg(feature = "reduced-round")))]
207pub type XChaCha8Poly1305 = ChaChaPoly1305<XChaCha8, U24>;
208
209/// XChaCha12Poly1305 (reduced round variant) Authenticated Encryption with Additional Data (AEAD).
210#[cfg(feature = "reduced-round")]
211#[cfg_attr(docsrs, doc(cfg(feature = "reduced-round")))]
212pub type XChaCha12Poly1305 = ChaChaPoly1305<XChaCha12, U24>;
213
214/// Generic ChaCha+Poly1305 Authenticated Encryption with Additional Data (AEAD) construction.
215///
216/// See the [toplevel documentation](index.html) for a usage example.
217pub struct ChaChaPoly1305<C, N: ArraySize = U12> {
218 /// Secret key.
219 key: Key,
220
221 /// ChaCha stream cipher.
222 stream_cipher: PhantomData<C>,
223
224 /// Nonce size.
225 nonce_size: PhantomData<N>,
226}
227
228impl<C, N> KeySizeUser for ChaChaPoly1305<C, N>
229where
230 N: ArraySize,
231{
232 type KeySize = U32;
233}
234
235impl<C, N> KeyInit for ChaChaPoly1305<C, N>
236where
237 N: ArraySize,
238{
239 #[inline]
240 fn new(key: &Key) -> Self {
241 Self {
242 key: *key,
243 stream_cipher: PhantomData,
244 nonce_size: PhantomData,
245 }
246 }
247}
248
249impl<C, N> AeadCore for ChaChaPoly1305<C, N>
250where
251 N: ArraySize,
252{
253 type NonceSize = N;
254 type TagSize = U16;
255 const TAG_POSITION: TagPosition = TagPosition::Postfix;
256}
257
258impl<C, N> AeadInOut for ChaChaPoly1305<C, N>
259where
260 C: KeyIvInit<KeySize = U32, IvSize = N> + StreamCipher + StreamCipherSeek,
261 N: ArraySize,
262{
263 fn encrypt_inout_detached(
264 &self,
265 nonce: &aead::Nonce<Self>,
266 associated_data: &[u8],
267 buffer: InOutBuf<'_, '_, u8>,
268 ) -> Result<Tag, Error> {
269 Cipher::new(C::new(&self.key, nonce)).encrypt_inout_detached(associated_data, buffer)
270 }
271
272 fn decrypt_inout_detached(
273 &self,
274 nonce: &aead::Nonce<Self>,
275 associated_data: &[u8],
276 buffer: InOutBuf<'_, '_, u8>,
277 tag: &Tag,
278 ) -> Result<(), Error> {
279 Cipher::new(C::new(&self.key, nonce)).decrypt_inout_detached(associated_data, buffer, tag)
280 }
281}
282
283impl<C, N> Clone for ChaChaPoly1305<C, N>
284where
285 N: ArraySize,
286{
287 fn clone(&self) -> Self {
288 Self {
289 key: self.key,
290 stream_cipher: PhantomData,
291 nonce_size: PhantomData,
292 }
293 }
294}
295
296impl<C, N> Drop for ChaChaPoly1305<C, N>
297where
298 N: ArraySize,
299{
300 fn drop(&mut self) {
301 #[cfg(feature = "zeroize")]
302 {
303 use zeroize::Zeroize;
304 self.key.as_mut_slice().zeroize();
305 }
306 }
307}
308
309impl<C, N> fmt::Debug for ChaChaPoly1305<C, N>
310where
311 N: ArraySize,
312{
313 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314 f.debug_struct("ChaChaPoly1305").finish_non_exhaustive()
315 }
316}
317
318#[cfg(feature = "zeroize")]
319impl<C, N: ArraySize> zeroize::ZeroizeOnDrop for ChaChaPoly1305<C, N> {}