Skip to main content

bitwarden_crypto/hazmat/symmetric_encryption/
aes_gcm.rs

1//! # AES-256-GCM operations
2//!
3//! Contains low level AES-256-GCM operations used by the rest of the crate.
4//!
5//! In most cases you should use the [EncString][crate::EncString] with
6//! [KeyEncryptable][crate::KeyEncryptable] & [KeyDecryptable][crate::KeyDecryptable] instead.
7//!
8//! Note:
9//! AES-256-GCM encrypts data, and authenticates both the cipher text and associated data. This does
10//! not provide key-commitment, and assumes there can only be one key. It also has a short (96-bit)
11//! nonce, so a fresh key must be used per (small) set of messages to avoid nonce reuse; callers
12//! that derive a unique key per message (e.g. the secret-protected key envelope) satisfy this.
13//!
14//! If multiple keys are possible, a key-committing cipher should be used to prevent
15//! invisible-salamander style attacks.
16//! `https://eprint.iacr.org/2019/016.pdf`
17//! `https://soatok.blog/2024/09/10/invisible-salamanders-are-not-what-you-think/`
18
19use aes::cipher::common::Generate;
20use aes_gcm::{AeadCore, AeadInOut, Aes256Gcm as Aes256GcmAlg, KeyInit, aead::Nonce};
21use coset::{CoseEncrypt, CoseEncrypt0};
22use typenum::Unsigned;
23
24use super::Aead;
25use crate::CryptoError;
26
27pub(crate) const NONCE_SIZE: usize = <Aes256GcmAlg as AeadCore>::NonceSize::USIZE;
28pub(crate) const KEY_SIZE: usize = 32;
29
30/// AES-256-GCM authenticated encryption with associated data.
31///
32/// See the [module documentation](self) for the security caveats (no key-commitment, short nonce)
33/// that apply to this cipher.
34pub(crate) struct Aes256Gcm;
35
36impl Aead for Aes256Gcm {
37    type Key = [u8; KEY_SIZE];
38    type Ciphertext = Aes256GcmCiphertext;
39    type Nonce = Aes256GcmNonce;
40
41    fn encrypt(
42        key: &Self::Key,
43        nonce: &Self::Nonce,
44        plaintext: &[u8],
45        associated_data: &[u8],
46    ) -> Self::Ciphertext {
47        // This buffer contains the plaintext, that will be encrypted in-place
48        let mut buffer = plaintext.to_vec();
49        Aes256GcmAlg::new(key.into())
50            .encrypt_in_place(&nonce.0, associated_data, &mut buffer)
51            .expect("encryption failed");
52
53        Aes256GcmCiphertext {
54            encrypted_bytes: buffer,
55        }
56    }
57
58    fn decrypt(
59        key: &Self::Key,
60        nonce: &Self::Nonce,
61        ciphertext: &Self::Ciphertext,
62        associated_data: &[u8],
63    ) -> Result<Vec<u8>, CryptoError> {
64        let mut buffer = ciphertext.encrypted_bytes().to_vec();
65        Aes256GcmAlg::new(key.into())
66            .decrypt_in_place(&nonce.0, associated_data, &mut buffer)
67            .map_err(|_| CryptoError::KeyDecrypt)?;
68        Ok(buffer)
69    }
70}
71
72/// A 96-bit AES-256-GCM nonce.
73///
74/// AES-256-GCM has a short (96-bit) nonce, so a fresh nonce must be used for every message
75/// encrypted under a given key to avoid catastrophic nonce reuse. Generate a fresh nonce with
76/// [`Aes256GcmNonce::make`] for each encryption.
77pub(crate) struct Aes256GcmNonce(Nonce<Aes256GcmAlg>);
78
79impl Aes256GcmNonce {
80    /// Generates a fresh, cryptographically random nonce.
81    pub(crate) fn make() -> Self {
82        let mut rng = rand::rng();
83        Aes256GcmNonce(Nonce::<Aes256GcmAlg>::generate_from_rng(&mut rng))
84    }
85
86    /// Returns the raw nonce bytes.
87    pub(crate) fn as_bytes(&self) -> &[u8] {
88        self.0.as_slice()
89    }
90
91    /// Parses the nonce from a COSE message's unprotected `iv` header bytes.
92    fn from_cose_iv(iv: &[u8]) -> Result<Self, CryptoError> {
93        let nonce: [u8; NONCE_SIZE] = iv.try_into().map_err(|_| CryptoError::InvalidNonceLength)?;
94        Ok(Aes256GcmNonce(nonce.into()))
95    }
96}
97
98/// Parses the nonce from the unprotected `iv` header of a [`CoseEncrypt`] message.
99impl TryFrom<&CoseEncrypt> for Aes256GcmNonce {
100    type Error = CryptoError;
101
102    fn try_from(cose_encrypt: &CoseEncrypt) -> Result<Self, Self::Error> {
103        Self::from_cose_iv(cose_encrypt.unprotected.iv.as_slice())
104    }
105}
106
107/// Parses the nonce from the unprotected `iv` header of a [`CoseEncrypt0`] message.
108impl TryFrom<&CoseEncrypt0> for Aes256GcmNonce {
109    type Error = CryptoError;
110
111    fn try_from(cose_encrypt0: &CoseEncrypt0) -> Result<Self, Self::Error> {
112        Self::from_cose_iv(cose_encrypt0.unprotected.iv.as_slice())
113    }
114}
115
116pub(crate) struct Aes256GcmCiphertext {
117    encrypted_bytes: Vec<u8>,
118}
119
120impl Aes256GcmCiphertext {
121    pub(crate) fn encrypted_bytes(&self) -> &[u8] {
122        &self.encrypted_bytes
123    }
124}
125
126/// Wraps already-encrypted bytes (e.g. read from a COSE message) so they can be passed to
127/// [`Aes256Gcm::decrypt`](Aead::decrypt).
128impl From<Vec<u8>> for Aes256GcmCiphertext {
129    fn from(encrypted_bytes: Vec<u8>) -> Self {
130        Aes256GcmCiphertext { encrypted_bytes }
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    #[cfg(test)]
137    use super::*;
138
139    #[test]
140    fn test_encrypt_decrypt_aes256_gcm() {
141        let key = [0u8; KEY_SIZE];
142        let nonce = Aes256GcmNonce::make();
143        let plaintext_secret_data = b"My secret data";
144        let authenticated_data = b"My authenticated data";
145        let encrypted = Aes256Gcm::encrypt(&key, &nonce, plaintext_secret_data, authenticated_data);
146        let decrypted = Aes256Gcm::decrypt(&key, &nonce, &encrypted, authenticated_data).unwrap();
147        assert_eq!(plaintext_secret_data, decrypted.as_slice());
148    }
149
150    #[test]
151    fn test_make_nonce_has_correct_length() {
152        let nonce = Aes256GcmNonce::make();
153        assert_eq!(nonce.as_bytes().len(), NONCE_SIZE);
154    }
155
156    #[test]
157    fn test_fails_when_ciphertext_changed() {
158        let key = [0u8; KEY_SIZE];
159        let nonce = Aes256GcmNonce::make();
160        let plaintext_secret_data = b"My secret data";
161        let authenticated_data = b"My authenticated data";
162
163        let mut encrypted =
164            Aes256Gcm::encrypt(&key, &nonce, plaintext_secret_data, authenticated_data);
165        encrypted.encrypted_bytes[0] = encrypted.encrypted_bytes[0].wrapping_add(1);
166        let result = Aes256Gcm::decrypt(&key, &nonce, &encrypted, authenticated_data);
167        assert!(result.is_err());
168    }
169
170    #[test]
171    fn test_fails_when_associated_data_changed() {
172        let key = [0u8; KEY_SIZE];
173        let nonce = Aes256GcmNonce::make();
174        let plaintext_secret_data = b"My secret data";
175        let mut authenticated_data = b"My authenticated data".to_vec();
176
177        let encrypted = Aes256Gcm::encrypt(
178            &key,
179            &nonce,
180            plaintext_secret_data,
181            authenticated_data.as_slice(),
182        );
183        authenticated_data[0] = authenticated_data[0].wrapping_add(1);
184        let result = Aes256Gcm::decrypt(&key, &nonce, &encrypted, authenticated_data.as_slice());
185        assert!(result.is_err());
186    }
187
188    #[test]
189    fn test_fails_when_nonce_changed() {
190        let key = [0u8; KEY_SIZE];
191        let nonce = Aes256GcmNonce::make();
192        let plaintext_secret_data = b"My secret data";
193        let authenticated_data = b"My authenticated data";
194
195        let encrypted = Aes256Gcm::encrypt(&key, &nonce, plaintext_secret_data, authenticated_data);
196        // Decrypting with a different (freshly generated) nonce must fail.
197        let other_nonce = Aes256GcmNonce::make();
198        let result = Aes256Gcm::decrypt(&key, &other_nonce, &encrypted, authenticated_data);
199        assert!(result.is_err());
200    }
201}