1use crate::cipher::chacha20::quarter_round;
23use crate::cipher::chacha20poly1305::ChaCha20Poly1305;
24
25fn hchacha20(key: &[u8; 32], input: &[u8; 16]) -> [u8; 32] {
34 let mut state = [0u32; 16];
35
36 state[0] = 0x6170_7865;
38 state[1] = 0x3320_646e;
39 state[2] = 0x7962_2d32;
40 state[3] = 0x6b20_6574;
41
42 for i in 0..8 {
44 state[4 + i] = u32::from_le_bytes(key[4 * i..4 * i + 4].try_into().unwrap());
45 }
46
47 for i in 0..4 {
50 state[12 + i] = u32::from_le_bytes(input[4 * i..4 * i + 4].try_into().unwrap());
51 }
52
53 for _ in 0..10 {
55 quarter_round(&mut state, 0, 4, 8, 12);
56 quarter_round(&mut state, 1, 5, 9, 13);
57 quarter_round(&mut state, 2, 6, 10, 14);
58 quarter_round(&mut state, 3, 7, 11, 15);
59 quarter_round(&mut state, 0, 5, 10, 15);
60 quarter_round(&mut state, 1, 6, 11, 12);
61 quarter_round(&mut state, 2, 7, 8, 13);
62 quarter_round(&mut state, 3, 4, 9, 14);
63 }
64
65 let mut out = [0u8; 32];
67 for i in 0..4 {
68 out[4 * i..4 * i + 4].copy_from_slice(&state[i].to_le_bytes());
69 }
70 for i in 0..4 {
71 out[16 + 4 * i..16 + 4 * i + 4].copy_from_slice(&state[12 + i].to_le_bytes());
72 }
73 out
74}
75
76pub struct XChaCha20Poly1305;
86
87impl XChaCha20Poly1305 {
88 pub fn encrypt(key: &[u8; 32], nonce: &[u8; 24], aad: &[u8], plaintext: &[u8]) -> (Vec<u8>, [u8; 16]) {
93 let (subkey, nonce12) = derive(key, nonce);
94 ChaCha20Poly1305::encrypt(&subkey, &nonce12, aad, plaintext)
95 }
96
97 pub fn decrypt(key: &[u8; 32], nonce: &[u8; 24], aad: &[u8], ciphertext: &[u8], tag: &[u8; 16]) -> Option<Vec<u8>> {
99 let (subkey, nonce12) = derive(key, nonce);
100 ChaCha20Poly1305::decrypt(&subkey, &nonce12, aad, ciphertext, tag)
101 }
102}
103
104fn derive(key: &[u8; 32], nonce: &[u8; 24]) -> ([u8; 32], [u8; 12]) {
106 let mut hchacha_in = [0u8; 16];
107 hchacha_in.copy_from_slice(&nonce[..16]);
108 let subkey = hchacha20(key, &hchacha_in);
109
110 let mut nonce12 = [0u8; 12];
111 nonce12[4..].copy_from_slice(&nonce[16..24]);
113
114 (subkey, nonce12)
115}
116
117#[cfg(test)]
122mod tests {
123 use super::*;
124
125 fn hex(s: &str) -> Vec<u8> {
126 let s: String = s.chars().filter(|c| !c.is_whitespace()).collect();
127 (0..s.len())
128 .step_by(2)
129 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
130 .collect()
131 }
132
133 #[test]
135 fn hchacha20_test_vector() {
136 let key = hex("00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
137 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f");
138 let input = hex("00 00 00 09 00 00 00 4a 00 00 00 00 31 41 59 27");
139 let expected = hex("82 41 3b 42 27 b2 7b fe d3 0e 42 50 8a 87 7d 73
140 a0 f9 e4 d5 8a 74 a8 53 c1 2e c4 13 26 d3 ec dc");
141 let k: [u8; 32] = key.try_into().unwrap();
142 let n: [u8; 16] = input.try_into().unwrap();
143 let out = hchacha20(&k, &n);
144 assert_eq!(out.to_vec(), expected);
145 }
146
147 #[test]
149 fn xchacha20poly1305_test_vector() {
150 let plaintext = hex("4c 61 64 69 65 73 20 61 6e 64 20 47 65 6e 74 6c
151 65 6d 65 6e 20 6f 66 20 74 68 65 20 63 6c 61 73
152 73 20 6f 66 20 27 39 39 3a 20 49 66 20 49 20 63
153 6f 75 6c 64 20 6f 66 66 65 72 20 79 6f 75 20 6f
154 6e 6c 79 20 6f 6e 65 20 74 69 70 20 66 6f 72 20
155 74 68 65 20 66 75 74 75 72 65 2c 20 73 75 6e 73
156 63 72 65 65 6e 20 77 6f 75 6c 64 20 62 65 20 69
157 74 2e");
158 let aad = hex("50 51 52 53 c0 c1 c2 c3 c4 c5 c6 c7");
159 let key = hex("80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f
160 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f");
161 let nonce = hex("40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f
162 50 51 52 53 54 55 56 57");
163 let expected_ct = hex("bd 6d 17 9d 3e 83 d4 3b 95 76 57 94 93 c0 e9 39
164 57 2a 17 00 25 2b fa cc be d2 90 2c 21 39 6c bb
165 73 1c 7f 1b 0b 4a a6 44 0b f3 a8 2f 4e da 7e 39
166 ae 64 c6 70 8c 54 c2 16 cb 96 b7 2e 12 13 b4 52
167 2f 8c 9b a4 0d b5 d9 45 b1 1b 69 b9 82 c1 bb 9e
168 3f 3f ac 2b c3 69 48 8f 76 b2 38 35 65 d3 ff f9
169 21 f9 66 4c 97 63 7d a9 76 88 12 f6 15 c6 8b 13
170 b5 2e");
171 let expected_tag = hex("c0 87 59 24 c1 c7 98 79 47 de af d8 78 0a cf 49");
172
173 let k: [u8; 32] = key.try_into().unwrap();
174 let n: [u8; 24] = nonce.try_into().unwrap();
175
176 let (ct, tag) = XChaCha20Poly1305::encrypt(&k, &n, &aad, &plaintext);
177 assert_eq!(ct, expected_ct, "ciphertext mismatch");
178 assert_eq!(tag.to_vec(), expected_tag, "tag mismatch");
179
180 let pt = XChaCha20Poly1305::decrypt(&k, &n, &aad, &ct, &tag).expect("decrypt must succeed");
181 assert_eq!(pt, plaintext);
182 }
183
184 #[test]
185 fn xchacha20poly1305_tamper_rejected() {
186 let key = [0x42u8; 32];
187 let nonce = [0x77u8; 24];
188 let aad = b"header";
189 let pt = b"secret payload";
190
191 let (mut ct, tag) = XChaCha20Poly1305::encrypt(&key, &nonce, aad, pt);
192 ct[0] ^= 0xFF;
194 assert!(XChaCha20Poly1305::decrypt(&key, &nonce, aad, &ct, &tag).is_none());
195 }
196
197 #[test]
198 fn xchacha20poly1305_empty_plaintext() {
199 let key = [0u8; 32];
200 let nonce = [0u8; 24];
201 let (ct, tag) = XChaCha20Poly1305::encrypt(&key, &nonce, b"", b"");
202 assert_eq!(ct.len(), 0);
203 let pt = XChaCha20Poly1305::decrypt(&key, &nonce, b"", &ct, &tag).unwrap();
204 assert_eq!(pt.len(), 0);
205 }
206}