bitwarden_crypto/
xchacha20.rs1use aes::cipher::common::Generate;
18use chacha20poly1305::{AeadCore, AeadInOut, KeyInit, XChaCha20Poly1305, aead::Nonce};
19use typenum::Unsigned;
20
21use crate::CryptoError;
22
23pub(crate) const NONCE_SIZE: usize = <XChaCha20Poly1305 as AeadCore>::NonceSize::USIZE;
24pub(crate) const KEY_SIZE: usize = 32;
25
26pub(crate) struct XChaCha20Poly1305Ciphertext {
27 nonce: Nonce<XChaCha20Poly1305>,
28 encrypted_bytes: Vec<u8>,
29}
30
31impl XChaCha20Poly1305Ciphertext {
32 pub(crate) fn nonce(&self) -> [u8; NONCE_SIZE] {
33 self.nonce.into()
34 }
35
36 pub(crate) fn encrypted_bytes(&self) -> &[u8] {
37 &self.encrypted_bytes
38 }
39}
40
41pub(crate) fn encrypt_xchacha20_poly1305(
42 key: &[u8; KEY_SIZE],
43 plaintext_secret_data: &[u8],
44 associated_data: &[u8],
45) -> XChaCha20Poly1305Ciphertext {
46 let rng = rand::rng();
47 encrypt_xchacha20_poly1305_internal(rng, key, plaintext_secret_data, associated_data)
48}
49
50fn encrypt_xchacha20_poly1305_internal(
51 mut rng: impl rand::CryptoRng,
52 key: &[u8; KEY_SIZE],
53 plaintext_secret_data: &[u8],
54 associated_data: &[u8],
55) -> XChaCha20Poly1305Ciphertext {
56 let nonce = Nonce::<XChaCha20Poly1305>::generate_from_rng(&mut rng);
57 let mut buffer = plaintext_secret_data.to_vec();
59 XChaCha20Poly1305::new(key.into())
60 .encrypt_in_place(&nonce, associated_data, &mut buffer)
61 .expect("encryption failed");
62
63 XChaCha20Poly1305Ciphertext {
64 nonce,
65 encrypted_bytes: buffer,
66 }
67}
68
69pub(crate) fn decrypt_xchacha20_poly1305(
70 nonce: &[u8; NONCE_SIZE],
71 key: &[u8; KEY_SIZE],
72 ciphertext: &[u8],
73 associated_data: &[u8],
74) -> Result<Vec<u8>, CryptoError> {
75 let mut buffer = ciphertext.to_vec();
76 XChaCha20Poly1305::new(key.into())
77 .decrypt_in_place(nonce.into(), associated_data, &mut buffer)
78 .map_err(|_| CryptoError::KeyDecrypt)?;
79 Ok(buffer)
80}
81
82mod tests {
83 #[cfg(test)]
84 use crate::xchacha20::*;
85
86 #[test]
87 fn test_encrypt_decrypt_xchacha20() {
88 let key = [0u8; KEY_SIZE];
89 let plaintext_secret_data = b"My secret data";
90 let authenticated_data = b"My authenticated data";
91 let encrypted = encrypt_xchacha20_poly1305(&key, plaintext_secret_data, authenticated_data);
92 let decrypted = decrypt_xchacha20_poly1305(
93 &encrypted.nonce.into(),
94 &key,
95 &encrypted.encrypted_bytes,
96 authenticated_data,
97 )
98 .unwrap();
99 assert_eq!(plaintext_secret_data, decrypted.as_slice());
100 }
101
102 #[test]
103 fn test_fails_when_ciphertext_changed() {
104 let key = [0u8; KEY_SIZE];
105 let plaintext_secret_data = b"My secret data";
106 let authenticated_data = b"My authenticated data";
107
108 let mut encrypted =
109 encrypt_xchacha20_poly1305(&key, plaintext_secret_data, authenticated_data);
110 encrypted.encrypted_bytes[0] = encrypted.encrypted_bytes[0].wrapping_add(1);
111 let result = decrypt_xchacha20_poly1305(
112 &encrypted.nonce.into(),
113 &key,
114 &encrypted.encrypted_bytes,
115 authenticated_data,
116 );
117 assert!(result.is_err());
118 }
119
120 #[test]
121 fn test_fails_when_associated_data_changed() {
122 let key = [0u8; KEY_SIZE];
123 let plaintext_secret_data = b"My secret data";
124 let mut authenticated_data = b"My authenticated data".to_vec();
125
126 let encrypted =
127 encrypt_xchacha20_poly1305(&key, plaintext_secret_data, authenticated_data.as_slice());
128 authenticated_data[0] = authenticated_data[0].wrapping_add(1);
129 let result = decrypt_xchacha20_poly1305(
130 &encrypted.nonce.into(),
131 &key,
132 &encrypted.encrypted_bytes,
133 authenticated_data.as_slice(),
134 );
135 assert!(result.is_err());
136 }
137
138 #[test]
139 fn test_fails_when_nonce_changed() {
140 let key = [0u8; KEY_SIZE];
141 let plaintext_secret_data = b"My secret data";
142 let authenticated_data = b"My authenticated data";
143
144 let mut encrypted =
145 encrypt_xchacha20_poly1305(&key, plaintext_secret_data, authenticated_data);
146 encrypted.nonce[0] = encrypted.nonce[0].wrapping_add(1);
147 let result = decrypt_xchacha20_poly1305(
148 &encrypted.nonce.into(),
149 &key,
150 &encrypted.encrypted_bytes,
151 authenticated_data,
152 );
153 assert!(result.is_err());
154 }
155}