libcrux/
aead.rs

1//! # AEAD
2//!
3//! Depending on the platform and available features the most efficient implementation
4//! is chosen.
5//!
6//! ## ChaCha20Poly1305
7//!
8//! The HACL implementations are used on all platforms.
9//! On x64 CPUs with AVX and AVX2 support the 256-bit SIMD implementation is used.
10//! On CPUs with a 128-bit SIMD unit (arm64, or SSE3, SSE4.1, and AVX on x64), the
11//! 128-bit SIMD implementation is used.
12//! In any other case the portable implementation is used.
13
14#[cfg(aes_ni)]
15use crate::hacl::aesgcm;
16use crate::hacl::chacha20_poly1305;
17
18use libcrux_platform::{aes_ni_support, simd128_support, simd256_support};
19
20/// The AEAD Errors.
21#[derive(Debug, PartialEq, Eq, Clone, Copy)]
22pub enum Error {
23    UnsupportedAlgorithm,
24    EncryptionError,
25    InvalidKey,
26    DecryptionFailed,
27    InvalidIv,
28    InvalidTag,
29}
30
31/// The AEAD Algorithm Identifier.
32#[derive(Clone, Copy, PartialEq, Debug)]
33#[repr(u32)]
34pub enum Algorithm {
35    /// AES GCM 128
36    Aes128Gcm = 1,
37
38    /// AES GCM 256
39    Aes256Gcm = 2,
40
41    /// ChaCha20 Poly1305
42    Chacha20Poly1305 = 3,
43}
44
45impl From<u8> for Algorithm {
46    fn from(v: u8) -> Algorithm {
47        match v {
48            0 => Algorithm::Aes128Gcm,
49            1 => Algorithm::Aes256Gcm,
50            2 => Algorithm::Chacha20Poly1305,
51            _ => panic!("Unknown AEAD mode {}", v),
52        }
53    }
54}
55
56impl Algorithm {
57    /// Get the key size of the `Algorithm` in bytes.
58    #[inline]
59    pub const fn key_size(self) -> usize {
60        match self {
61            Algorithm::Aes128Gcm => 16,
62            Algorithm::Aes256Gcm => 32,
63            Algorithm::Chacha20Poly1305 => 32,
64        }
65    }
66
67    /// Get the tag size of the `Algorithm` in bytes.
68    #[inline]
69    pub const fn tag_size(self) -> usize {
70        match self {
71            Algorithm::Aes128Gcm => 16,
72            Algorithm::Aes256Gcm => 16,
73            Algorithm::Chacha20Poly1305 => 16,
74        }
75    }
76
77    /// Get the nonce size of the `Algorithm` in bytes.
78    #[inline]
79    pub const fn nonce_size(self) -> usize {
80        match self {
81            Algorithm::Aes128Gcm => 12,
82            Algorithm::Aes256Gcm => 12,
83            Algorithm::Chacha20Poly1305 => 12,
84        }
85    }
86
87    /// Make sure the algorithm is supported by the hardware.
88    #[inline]
89    fn supported(self) -> Result<(), Error> {
90        if matches!(self, Algorithm::Aes128Gcm | Algorithm::Aes256Gcm) && !aes_ni_support() {
91            Err(Error::UnsupportedAlgorithm)
92        } else {
93            Ok(())
94        }
95    }
96}
97
98#[derive(Default)]
99pub struct Aes128Key(pub [u8; Algorithm::key_size(Algorithm::Aes128Gcm)]);
100
101#[derive(Default)]
102pub struct Aes256Key(pub [u8; Algorithm::key_size(Algorithm::Aes256Gcm)]);
103
104#[derive(Default)]
105pub struct Chacha20Key(pub [u8; Algorithm::key_size(Algorithm::Chacha20Poly1305)]);
106
107mod keygen {
108    use super::*;
109    use rand::{CryptoRng, RngCore};
110
111    macro_rules! impl_key_gen {
112        ($name:ident) => {
113            impl $name {
114                pub fn generate(rng: &mut (impl RngCore + CryptoRng)) -> Self {
115                    let mut k = Self::default();
116                    rng.fill_bytes(&mut k.0);
117                    k
118                }
119            }
120        };
121    }
122    impl_key_gen!(Aes128Key);
123    impl_key_gen!(Aes256Key);
124    impl_key_gen!(Chacha20Key);
125
126    impl Key {
127        pub fn generate(alg: Algorithm, rng: &mut (impl RngCore + CryptoRng)) -> Self {
128            match alg {
129                Algorithm::Aes128Gcm => Self::Aes128(Aes128Key::generate(rng)),
130                Algorithm::Aes256Gcm => Self::Aes256(Aes256Key::generate(rng)),
131                Algorithm::Chacha20Poly1305 => Self::Chacha20Poly1305(Chacha20Key::generate(rng)),
132            }
133        }
134    }
135
136    impl Iv {
137        /// Generate a new random Iv
138        pub fn generate(rng: &mut (impl RngCore + CryptoRng)) -> Self {
139            let mut n = Self::default();
140            rng.fill_bytes(&mut n.0);
141            n
142        }
143
144        /// Wrap an array.
145        pub fn new(iv: impl AsRef<[u8]>) -> Result<Self, Error> {
146            Ok(Self(iv.as_ref().try_into().map_err(|_| Error::InvalidIv)?))
147        }
148    }
149}
150
151/// An AEAD key.
152pub enum Key {
153    Aes128(Aes128Key),
154    Aes256(Aes256Key),
155    Chacha20Poly1305(Chacha20Key),
156}
157
158impl Key {
159    /// Generate a [`Key`] for the [`Algorithm`] from the raw `bytes`.
160    pub fn from_bytes(alg: Algorithm, bytes: Vec<u8>) -> Result<Self, Error> {
161        alg.supported()?;
162
163        Ok(match alg {
164            Algorithm::Aes128Gcm => {
165                Self::Aes128(Aes128Key(bytes.try_into().map_err(|_| Error::InvalidKey)?))
166            }
167            Algorithm::Aes256Gcm => {
168                Self::Aes256(Aes256Key(bytes.try_into().map_err(|_| Error::InvalidKey)?))
169            }
170            Algorithm::Chacha20Poly1305 => Self::Chacha20Poly1305(Chacha20Key(
171                bytes.try_into().map_err(|_| Error::InvalidKey)?,
172            )),
173        })
174    }
175
176    /// Generate a [`Key`] for the [`Algorithm`] from the raw `bytes` slice.
177    pub fn from_slice(alg: Algorithm, bytes: impl AsRef<[u8]>) -> Result<Self, Error> {
178        alg.supported()?;
179
180        Ok(match alg {
181            Algorithm::Aes128Gcm => Self::Aes128(Aes128Key(
182                bytes.as_ref().try_into().map_err(|_| Error::InvalidKey)?,
183            )),
184            Algorithm::Aes256Gcm => Self::Aes256(Aes256Key(
185                bytes.as_ref().try_into().map_err(|_| Error::InvalidKey)?,
186            )),
187            Algorithm::Chacha20Poly1305 => Self::Chacha20Poly1305(Chacha20Key(
188                bytes.as_ref().try_into().map_err(|_| Error::InvalidKey)?,
189            )),
190        })
191    }
192}
193
194#[derive(Default, PartialEq, Eq, Debug)]
195pub struct Tag([u8; 16]);
196
197impl Tag {
198    /// Convert slice into a [`Tag`]
199    pub fn from_slice(t: impl AsRef<[u8]>) -> Result<Self, Error> {
200        Ok(Self(t.as_ref().try_into().map_err(|_| Error::InvalidTag)?))
201    }
202}
203
204impl From<[u8; 16]> for Tag {
205    fn from(value: [u8; 16]) -> Self {
206        Self(value)
207    }
208}
209
210impl AsRef<[u8]> for Tag {
211    fn as_ref(&self) -> &[u8] {
212        &self.0
213    }
214}
215
216impl AsMut<[u8]> for Tag {
217    fn as_mut(&mut self) -> &mut [u8] {
218        &mut self.0
219    }
220}
221
222#[derive(Default)]
223pub struct Iv(pub [u8; 12]);
224
225#[cfg(simd256)]
226fn encrypt_256(key: &Chacha20Key, msg_ctxt: &mut [u8], iv: Iv, aad: &[u8]) -> Tag {
227    chacha20_poly1305::simd256::encrypt(&key.0, msg_ctxt, iv.0, aad).into()
228}
229
230/// Fallback when simd256 is detected at runtime but it wasn't compiled.
231/// We try to fall back to simd128 in this case.
232#[cfg(not(simd256))]
233fn encrypt_256(key: &Chacha20Key, msg_ctxt: &mut [u8], iv: Iv, aad: &[u8]) -> Tag {
234    encrypt_128(key, msg_ctxt, iv, aad)
235}
236
237#[cfg(simd128)]
238fn encrypt_128(key: &Chacha20Key, msg_ctxt: &mut [u8], iv: Iv, aad: &[u8]) -> Tag {
239    chacha20_poly1305::simd128::encrypt(&key.0, msg_ctxt, iv.0, aad).into()
240}
241
242/// Fallback when simd128 is detected at runtime but it wasn't compiled.
243/// We try to fall back to portable in this case.
244#[cfg(not(simd128))]
245fn encrypt_128(key: &Chacha20Key, msg_ctxt: &mut [u8], iv: Iv, aad: &[u8]) -> Tag {
246    encrypt_32(key, msg_ctxt, iv, aad)
247}
248
249fn encrypt_32(key: &Chacha20Key, msg_ctxt: &mut [u8], iv: Iv, aad: &[u8]) -> Tag {
250    chacha20_poly1305::encrypt(&key.0, msg_ctxt, iv.0, aad).into()
251}
252
253#[cfg(simd256)]
254fn decrypt_256(
255    key: &Chacha20Key,
256    ctxt_msg: &mut [u8],
257    iv: Iv,
258    aad: &[u8],
259    tag: &Tag,
260) -> Result<(), Error> {
261    chacha20_poly1305::simd256::decrypt(&key.0, ctxt_msg, iv.0, aad, &tag.0)
262        .map_err(|_| Error::DecryptionFailed)
263}
264
265/// Fallback when simd256 is detected at runtime but it wasn't compiled.
266/// We try to fall back to simd128 in this case.
267#[cfg(not(simd256))]
268fn decrypt_256(
269    key: &Chacha20Key,
270    ctxt_msg: &mut [u8],
271    iv: Iv,
272    aad: &[u8],
273    tag: &Tag,
274) -> Result<(), Error> {
275    decrypt_128(key, ctxt_msg, iv, aad, tag)
276}
277
278#[cfg(simd128)]
279fn decrypt_128(
280    key: &Chacha20Key,
281    ctxt_msg: &mut [u8],
282    iv: Iv,
283    aad: &[u8],
284    tag: &Tag,
285) -> Result<(), Error> {
286    chacha20_poly1305::simd128::decrypt(&key.0, ctxt_msg, iv.0, aad, &tag.0)
287        .map_err(|_| Error::DecryptionFailed)
288}
289
290/// Fallback when simd128 is detected at runtime but it wasn't compiled.
291/// We try to fall back to portable in this case.
292#[cfg(not(simd128))]
293fn decrypt_128(
294    key: &Chacha20Key,
295    ctxt_msg: &mut [u8],
296    iv: Iv,
297    aad: &[u8],
298    tag: &Tag,
299) -> Result<(), Error> {
300    decrypt_32(key, ctxt_msg, iv, aad, tag)
301}
302
303fn decrypt_32(
304    key: &Chacha20Key,
305    ctxt_msg: &mut [u8],
306    iv: Iv,
307    aad: &[u8],
308    tag: &Tag,
309) -> Result<(), Error> {
310    chacha20_poly1305::decrypt(&key.0, ctxt_msg, iv.0, aad, &tag.0)
311        .map_err(|_| Error::DecryptionFailed)
312}
313
314#[cfg(aes_ni)]
315fn aes_encrypt_128(key: &Aes128Key, msg_ctxt: &mut [u8], iv: Iv, aad: &[u8]) -> Result<Tag, Error> {
316    aesgcm::encrypt_128(&key.0, msg_ctxt, iv.0, aad)
317        .map_err(|e| match e {
318            aesgcm::Error::UnsupportedHardware => Error::UnsupportedAlgorithm,
319            _ => Error::EncryptionError,
320        })
321        .map(|t| t.into())
322}
323
324#[cfg(not(aes_ni))]
325fn aes_encrypt_128(_: &Aes128Key, _: &mut [u8], _v: Iv, _: &[u8]) -> Result<Tag, Error> {
326    Err(Error::UnsupportedAlgorithm)
327}
328
329#[cfg(aes_ni)]
330fn aes_encrypt_256(key: &Aes256Key, msg_ctxt: &mut [u8], iv: Iv, aad: &[u8]) -> Result<Tag, Error> {
331    aesgcm::encrypt_256(&key.0, msg_ctxt, iv.0, aad)
332        .map_err(|e| match e {
333            aesgcm::Error::UnsupportedHardware => Error::UnsupportedAlgorithm,
334            _ => Error::EncryptionError,
335        })
336        .map(|t| t.into())
337}
338
339#[cfg(not(aes_ni))]
340fn aes_encrypt_256(_: &Aes256Key, _: &mut [u8], _: Iv, _: &[u8]) -> Result<Tag, Error> {
341    Err(Error::UnsupportedAlgorithm)
342}
343
344#[cfg(aes_ni)]
345fn aes_decrypt_128(
346    key: &Aes128Key,
347    ctxt_msg: &mut [u8],
348    iv: Iv,
349    aad: &[u8],
350    tag: &Tag,
351) -> Result<(), Error> {
352    aesgcm::decrypt_128(&key.0, ctxt_msg, iv.0, aad, &tag.0).map_err(|e| match e {
353        aesgcm::Error::UnsupportedHardware => Error::UnsupportedAlgorithm,
354        _ => Error::EncryptionError,
355    })
356}
357
358#[cfg(not(aes_ni))]
359fn aes_decrypt_128(_: &Aes128Key, _: &mut [u8], _: Iv, _: &[u8], _: &Tag) -> Result<(), Error> {
360    Err(Error::UnsupportedAlgorithm)
361}
362
363#[cfg(aes_ni)]
364fn aes_decrypt_256(
365    key: &Aes256Key,
366    ctxt_msg: &mut [u8],
367    iv: Iv,
368    aad: &[u8],
369    tag: &Tag,
370) -> Result<(), Error> {
371    aesgcm::decrypt_256(&key.0, ctxt_msg, iv.0, aad, &tag.0).map_err(|e| match e {
372        aesgcm::Error::UnsupportedHardware => Error::UnsupportedAlgorithm,
373        _ => Error::EncryptionError,
374    })
375}
376
377#[cfg(not(aes_ni))]
378fn aes_decrypt_256(_: &Aes256Key, _: &mut [u8], _: Iv, _: &[u8], _: &Tag) -> Result<(), Error> {
379    Err(Error::UnsupportedAlgorithm)
380}
381
382/// AEAD encrypt the message in `msg_ctxt` with the `key`, `iv` and `aad`.
383///
384/// Returns the `Tag` and the ciphertext in `msg_ctxt`.
385pub fn encrypt(key: &Key, msg_ctxt: &mut [u8], iv: Iv, aad: &[u8]) -> Result<Tag, Error> {
386    match key {
387        Key::Aes128(key) => {
388            if aes_ni_support() {
389                aes_encrypt_128(key, msg_ctxt, iv, aad)
390            } else {
391                Err(Error::UnsupportedAlgorithm)
392            }
393        }
394        Key::Aes256(key) => {
395            if aes_ni_support() {
396                aes_encrypt_256(key, msg_ctxt, iv, aad)
397            } else {
398                Err(Error::UnsupportedAlgorithm)
399            }
400        }
401        Key::Chacha20Poly1305(key) => Ok(if simd256_support() {
402            encrypt_256(key, msg_ctxt, iv, aad)
403        } else if simd128_support() {
404            encrypt_128(key, msg_ctxt, iv, aad)
405        } else {
406            encrypt_32(key, msg_ctxt, iv, aad)
407        }),
408    }
409}
410
411/// AEAD encrypt the message in `msg` with the `key`, `iv` and `aad`.
412///
413/// Returns the `Tag` and the ciphertext tuple.
414pub fn encrypt_detached(
415    key: &Key,
416    msg: impl AsRef<[u8]>,
417    iv: Iv,
418    aad: impl AsRef<[u8]>,
419) -> Result<(Tag, Vec<u8>), Error> {
420    let mut msg_ctxt = msg.as_ref().to_vec();
421    let tag = encrypt(key, &mut msg_ctxt, iv, aad.as_ref())?;
422    Ok((tag, msg_ctxt))
423}
424
425/// AEAD decrypt the ciphertext in `ctxt_msg` with the `key`, `iv`, `aad`, and
426/// `tag`.
427///
428/// Returns the plaintext in `ctxt_msg` or an error if the decryption fails.
429pub fn decrypt(key: &Key, ctxt_msg: &mut [u8], iv: Iv, aad: &[u8], tag: &Tag) -> Result<(), Error> {
430    match key {
431        Key::Aes128(key) => {
432            if aes_ni_support() {
433                aes_decrypt_128(key, ctxt_msg, iv, aad, tag)
434            } else {
435                Err(Error::UnsupportedAlgorithm)
436            }
437        }
438        Key::Aes256(key) => {
439            if aes_ni_support() {
440                aes_decrypt_256(key, ctxt_msg, iv, aad, tag)
441            } else {
442                Err(Error::UnsupportedAlgorithm)
443            }
444        }
445        Key::Chacha20Poly1305(key) => {
446            if simd256_support() {
447                decrypt_256(key, ctxt_msg, iv, aad, tag)
448            } else if simd128_support() {
449                decrypt_128(key, ctxt_msg, iv, aad, tag)
450            } else {
451                decrypt_32(key, ctxt_msg, iv, aad, tag)
452            }
453        }
454    }
455}
456
457/// AEAD decrypt the ciphertext in `ctxt` with the `key`, `iv`, `aad`, and
458/// `tag`.
459///
460/// Returns the plaintext in `ctxt_msg` or an error if the decryption fails.
461pub fn decrypt_detached(
462    key: &Key,
463    ctxt: impl AsRef<[u8]>,
464    iv: Iv,
465    aad: impl AsRef<[u8]>,
466    tag: &Tag,
467) -> Result<Vec<u8>, Error> {
468    let mut ctxt_msg = ctxt.as_ref().to_vec();
469    decrypt(key, &mut ctxt_msg, iv, aad.as_ref(), tag)?;
470    Ok(ctxt_msg)
471}