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