crypto/chacha/
chacha20_blake3.rs1#[cfg(feature = "zeroize")]
2use zeroize::{Zeroize, ZeroizeOnDrop};
3
4use crate::{Aead, AeadError, Hash, Hasher, StreamCipher, Xof, blake3::Blake3, chacha::ChaCha20Djb};
5
6#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
47pub struct ChaCha20Blake3 {
48 key: [u8; 32],
49}
50
51impl ChaCha20Blake3 {
52 pub fn new(key: &[u8; 32]) -> Self {
53 return ChaCha20Blake3 {
54 key: *key,
55 };
56 }
57}
58
59impl Aead for ChaCha20Blake3 {
60 const TAG_SIZE: usize = 32;
61 const NONCE_SIZE: usize = 32;
62
63 fn encrypt_in_place(&self, in_out: &mut [u8], nonce: &[u8], aad: &[u8]) -> Hash {
64 assert!(nonce.len() == 32, "nonce must be 32 bytes");
65
66 let mut kdf_out = [0u8; 72];
68 let mut blake3_kdf = Blake3::new_keyed(&self.key);
69 blake3_kdf.update(nonce);
70 blake3_kdf.finalize_xof().squeeze(&mut kdf_out);
71
72 let chacha20_key: &[u8; 32] = &kdf_out[..32].try_into().unwrap();
76 let authentication_key: &[u8; 32] = &kdf_out[32..64].try_into().unwrap();
77 let chacha20_nonce: &[u8; 8] = &kdf_out[64..].try_into().unwrap();
78
79 ChaCha20Djb::new(chacha20_key, chacha20_nonce).xor_keystream(in_out);
80
81 let mut mac_hasher = Blake3::new_keyed(authentication_key);
83 mac_hasher.update(aad);
84 mac_hasher.update(&(aad.len() as u64).to_le_bytes());
85 mac_hasher.update(&in_out);
86 mac_hasher.update(&(in_out.len() as u64).to_le_bytes());
87 let tag = mac_hasher.sum();
88
89 #[cfg(feature = "zeroize")]
90 kdf_out.zeroize();
91
92 return tag;
93 }
94
95 fn decrypt_in_place(&self, in_out: &mut [u8], nonce: &[u8], aad: &[u8], tag: &[u8]) -> Result<(), AeadError> {
96 if nonce.len() != 32 {
97 return Err(AeadError::InvalidNonce);
98 }
99
100 let mut kdf_out = [0u8; 72];
102 let mut blake3_kdf = Blake3::new_keyed(&self.key);
103 blake3_kdf.update(nonce);
104 blake3_kdf.finalize_xof().squeeze(&mut kdf_out);
105
106 let chacha20_key: &[u8; 32] = kdf_out[..32].try_into().unwrap();
110 let authentication_key: &[u8; 32] = kdf_out[32..64].try_into().unwrap();
111 let chacha20_nonce: &[u8; 8] = kdf_out[64..].try_into().unwrap();
112
113 let mut mac_hasher = Blake3::new_keyed(&authentication_key);
114 mac_hasher.update(aad);
115 mac_hasher.update(&(aad.len() as u64).to_le_bytes());
116 mac_hasher.update(in_out);
117 mac_hasher.update(&(in_out.len() as u64).to_le_bytes());
118 let mac = mac_hasher.sum();
119
120 if !constant_time_eq::constant_time_eq(mac.as_ref(), tag) {
121 return Err(AeadError::InvalidCiphertext);
122 }
123
124 ChaCha20Djb::new(&chacha20_key, &chacha20_nonce).xor_keystream(in_out);
125
126 #[cfg(feature = "zeroize")]
127 kdf_out.zeroize();
128
129 return Ok(());
130 }
131}
132
133#[cfg(test)]
134mod test {
135 use super::ChaCha20Blake3;
136 use crate::Aead;
137
138 struct Test {
139 plaintext: &'static str,
140 key: &'static str,
141 nonce: &'static str,
142 aad: &'static str,
143 ct: &'static str,
144 }
145
146 #[test]
147 fn aead_chacha20blake3_test_vectors() {
148 let tests = [
149 Test {
150 plaintext: "",
151 key: "0000000000000000000000000000000000000000000000000000000000000000",
152 nonce: "0000000000000000000000000000000000000000000000000000000000000000",
153 aad: "",
154 ct: "e074bcc1f324f0139dea37f8465aa7edf565f968aeae9bfa348c9a9c1c702ad2",
155 },
156 Test {
157 plaintext: "4368614368613230",
158 key: "0100000000000000000000000000000000000000000000000000000000000010",
159 nonce: "1000000000000000000000000000000000000000000000000000000000000001",
160 aad: "424c414b4533",
161 ct: "af4d5f3ac75f3753a764e5af1d3396f9f6f5b5ea94889665372f39a9ae7aa55aa3d77b69680bfe45",
162 },
163 Test {
164 plaintext: "b8f60975cd7057a003ac84df00d514624fe40cb7855c50dd6594f59b3a2580e5",
165 key: "3eb02a239a2a66de159b9bb5486ccc10a6f63ddf5862ef076650513372353622",
166 nonce: "719d34360dcf03dc7af6a4d1d9fd311b035cbc148241f1419f166537a5552aec",
167 aad: "c8d69ca92da6c5fd22f1805179fcd36cb7a9d45848fa346ba7118c2f34d23a48",
168 ct: "42c948ee385574606ce91ed09f4ef744c69b7101ef682aee8acdd14fd827499eefe6f43193680e3685f3fe0a702a0c19ceb8d7b539a02edec99783fdb9816eb5",
169 },
170 ];
171
172 for (i, test) in tests.iter().enumerate() {
173 let key: [u8; 32] = hex::decode(test.key).unwrap().try_into().unwrap();
174 let nonce: [u8; 32] = hex::decode(test.nonce).unwrap().try_into().unwrap();
175 let pt = hex::decode(test.plaintext).unwrap();
176 let aad = hex::decode(test.aad).unwrap();
177 let ct_tag = hex::decode(test.ct).unwrap();
178
179 let expected_ct = &ct_tag[..ct_tag.len() - 32];
180 let expected_tag = &ct_tag[ct_tag.len() - 32..];
181
182 let cipher = ChaCha20Blake3::new(&key);
183
184 let mut buf = pt.clone();
185 let tag = cipher.encrypt_in_place(&mut buf, &nonce, &aad);
186 assert_eq!(buf, expected_ct, "test {i}: ciphertext mismatch");
187 assert_eq!(tag.as_ref(), expected_tag, "test {i}: tag mismatch");
188
189 let mut buf2 = expected_ct.to_vec();
190 cipher.decrypt_in_place(&mut buf2, &nonce, &aad, expected_tag).unwrap();
191 assert_eq!(buf2, pt, "test {i}: plaintext mismatch");
192 }
193 }
194}