Skip to main content

chacha20_poly1305/
lib.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! # `ChaCha20` - `Poly1305`
4//!
5//! Combine the `ChaCha20` stream cipher with the `Poly1305` message authentication code
6//! to form an authenticated encryption with additional data (AEAD) algorithm.
7
8#![no_std]
9// Coding conventions.
10#![warn(missing_docs)]
11#![warn(deprecated_in_future)]
12#![doc(test(attr(warn(unused))))]
13// Exclude lints we don't think are valuable.
14#![allow(clippy::inline_always)] // Not sure yet if we should give up the inline always, possible that the LLVM knows better.
15#![cfg_attr(chacha20_poly1305_fuzz, allow(dead_code, unused_imports))]
16
17#[cfg(feature = "alloc")]
18extern crate alloc;
19#[cfg(feature = "std")]
20extern crate std;
21
22pub mod chacha20;
23pub mod poly1305;
24
25use chacha20::ChaCha20;
26use poly1305::Poly1305;
27
28pub use self::chacha20::{Key, Nonce};
29#[doc(no_inline)]
30pub use self::error::Error;
31
32/// Zero array for padding slices.
33const ZEROES: [u8; 16] = [0u8; 16];
34
35/// Encrypt and decrypt content along with an authentication tag.
36#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
37pub struct ChaCha20Poly1305 {
38    key: Key,
39    nonce: Nonce,
40}
41
42impl ChaCha20Poly1305 {
43    /// Make a new instance of a `ChaCha20Poly1305` AEAD.
44    pub const fn new(key: Key, nonce: Nonce) -> Self { Self { key, nonce } }
45
46    /// Encrypt content in place and return the `Poly1305` 16-byte authentication tag.
47    ///
48    /// # Parameters
49    ///
50    /// - `content` - the plaintext to be encrypted in place.
51    /// - `aad`     - the optional metadata covered by the authentication tag.
52    ///
53    /// # Returns
54    ///
55    /// The 16-byte authentication tag.
56    pub fn encrypt(self, content: &mut [u8], aad: Option<&[u8]>) -> [u8; 16] {
57        let mut chacha = ChaCha20::new_from_block(self.key, self.nonce, 1);
58        chacha.apply_keystream(content);
59        #[cfg(not(chacha20_poly1305_fuzz))]
60        let poly_key = {
61            let keystream = chacha.get_keystream(0);
62            let mut k = [0u8; 32];
63            k.copy_from_slice(&keystream[..32]);
64            k
65        };
66        #[cfg(chacha20_poly1305_fuzz)]
67        let poly_key = self.key.0;
68        let mut poly = Poly1305::new(poly_key);
69        let aad = aad.unwrap_or(&[]);
70        // AAD and ciphertext are padded if not 16-byte aligned.
71        poly.input(aad);
72        let aad_overflow = aad.len() % 16;
73        if aad_overflow > 0 {
74            poly.input(&ZEROES[0..(16 - aad_overflow)]);
75        }
76
77        poly.input(content);
78        let text_overflow = content.len() % 16;
79        if text_overflow > 0 {
80            poly.input(&ZEROES[0..(16 - text_overflow)]);
81        }
82
83        let len_buffer = encode_lengths(aad.len() as u64, content.len() as u64);
84        poly.input(&len_buffer);
85        poly.tag()
86    }
87
88    /// Decrypt the ciphertext in place if authentication tag is correct.
89    ///
90    /// # Parameters
91    ///
92    /// - `content` - Ciphertext to be decrypted in place.
93    /// - `tag`     - 16-byte authentication tag.
94    /// - `aad`     - Optional metadata covered by the authentication tag.
95    ///
96    /// # Errors
97    ///
98    /// Returns [`Error::UnauthenticatedAdditionalData`] if the computed authentication tag does
99    /// not match the provided tag.
100    pub fn decrypt(
101        self,
102        content: &mut [u8],
103        tag: [u8; 16],
104        aad: Option<&[u8]>,
105    ) -> Result<(), Error> {
106        #[cfg(not(chacha20_poly1305_fuzz))]
107        let poly_key = {
108            let chacha = ChaCha20::new_from_block(self.key, self.nonce, 0);
109            let keystream = chacha.get_keystream(0);
110            let mut k = [0u8; 32];
111            k.copy_from_slice(&keystream[..32]);
112            k
113        };
114        #[cfg(chacha20_poly1305_fuzz)]
115        let poly_key = self.key.0;
116        let mut poly = Poly1305::new(poly_key);
117        let aad = aad.unwrap_or(&[]);
118        poly.input(aad);
119        // AAD and ciphertext are padded if not 16-byte aligned.
120        let aad_overflow = aad.len() % 16;
121        if aad_overflow > 0 {
122            poly.input(&ZEROES[0..(16 - aad_overflow)]);
123        }
124        poly.input(content);
125        let msg_overflow = content.len() % 16;
126        if msg_overflow > 0 {
127            poly.input(&ZEROES[0..(16 - msg_overflow)]);
128        }
129
130        let len_buffer = encode_lengths(aad.len() as u64, content.len() as u64);
131        poly.input(&len_buffer);
132        let derived_tag = poly.tag();
133        if derived_tag == tag {
134            let mut chacha = ChaCha20::new_from_block(self.key, self.nonce, 1);
135            chacha.apply_keystream(content);
136            Ok(())
137        } else {
138            Err(Error::UnauthenticatedAdditionalData)
139        }
140    }
141}
142
143/// AAD and content lengths are each encoded in 8-bytes.
144fn encode_lengths(aad_len: u64, content_len: u64) -> [u8; 16] {
145    let aad_len_bytes = aad_len.to_le_bytes();
146    let content_len_bytes = content_len.to_le_bytes();
147    let mut len_buffer = [0u8; 16];
148    let (aad_len_buffer, content_len_buffer) = len_buffer.split_at_mut(8);
149    aad_len_buffer.copy_from_slice(&aad_len_bytes[..]);
150    content_len_buffer.copy_from_slice(&content_len_bytes[..]);
151
152    len_buffer
153}
154
155/// Error types for the `ChaCha20Poly1305` AEAD.
156pub mod error {
157    use core::fmt;
158
159    /// Errors encrypting and decrypting messages with `ChaCha20` and `Poly1305` authentication tags.
160    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
161    pub enum Error {
162        /// Additional data showing up when it is not expected.
163        UnauthenticatedAdditionalData,
164    }
165
166    impl fmt::Display for Error {
167        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168            match self {
169                Self::UnauthenticatedAdditionalData => write!(f, "Unauthenticated aad."),
170            }
171        }
172    }
173
174    #[cfg(feature = "std")]
175    impl std::error::Error for Error {
176        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
177            match self {
178                Self::UnauthenticatedAdditionalData => None,
179            }
180        }
181    }
182}
183
184#[cfg(test)]
185#[cfg(feature = "alloc")]
186mod tests {
187    use alloc::vec::Vec;
188
189    use hex::prelude::*;
190
191    use super::*;
192
193    #[cfg(not(chacha20_poly1305_fuzz))]
194    #[test]
195    fn rfc7539() {
196        let mut message = *b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
197        let aad = Vec::from_hex("50515253c0c1c2c3c4c5c6c7").unwrap();
198        let key = Key::new(
199            Vec::from_hex("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
200                .unwrap()
201                .try_into()
202                .unwrap(),
203        );
204        let nonce =
205            Nonce::new(Vec::from_hex("070000004041424344454647").unwrap().try_into().unwrap());
206        let cipher = ChaCha20Poly1305::new(key, nonce);
207        let tag = cipher.encrypt(&mut message, Some(&aad));
208
209        let mut buffer = [0u8; 130];
210        buffer[..message.len()].copy_from_slice(&message);
211        buffer[message.len()..].copy_from_slice(&tag);
212
213        assert_eq!(&buffer.to_lower_hex_string(), "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd0600691");
214    }
215}