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