Skip to main content

hctr2_rs/
chctr2.rs

1#![allow(deprecated)]
2//! CHCTR2 (Cascaded HCTR2) beyond-birthday-bound secure wide-block tweakable cipher.
3//!
4//! CHCTR2 achieves 2n/3-bit multi-user security (approximately 85 bits with 128-bit blocks)
5//! by cascading HCTR2 twice with two independent keys. This provides significantly higher
6//! security than standard HCTR2's birthday-bound (64-bit) security.
7//!
8//! Construction (from "Beyond-Birthday-Bound Security with HCTR2", ASIACRYPT 2025):
9//! - Uses two independent keys K1 and K2
10//! - CHCTR2[K1,K2](T,M) = HCTR2[K2](T, HCTR2[K1](T, M))
11//! - Optimized: middle hash layer combines H1 and H2: Z_{1,2} = H1(T,R) XOR H2(T,R)
12//! - Cost per block: 2 BC calls + 3 field multiplications
13//!
14//! Security properties:
15//! - Beyond-birthday-bound: ~85-bit security vs HCTR2's ~64-bit
16//! - No restrictions on tweak usage
17//! - Multi-user secure
18//! - Ciphertext length equals plaintext length
19
20#[allow(deprecated)]
21use aes::cipher::{Array, BlockCipherDecrypt, BlockCipherEncrypt};
22use aes::{Aes128, Aes256};
23use polyval::{Polyval, universal_hash::UniversalHash};
24
25use crate::common::{BLOCK_LENGTH, Direction, Error, absorb, xctr, xor_blocks, xor_blocks_3};
26use crate::hctr2::AesCipher;
27
28// Keep the old error type as an alias for backwards compatibility
29#[allow(non_camel_case_types)]
30#[deprecated(note = "Use common::Error instead")]
31pub type Chctr2Error = Error;
32
33/// Generic CHCTR2 cipher parameterized by AES key size.
34pub struct Chctr2<Aes: AesCipher> {
35    ks1_enc: Aes,
36    ks1_dec: Aes::Dec,
37    h1: [u8; BLOCK_LENGTH],
38    l1: [u8; BLOCK_LENGTH],
39    ks2_enc: Aes,
40    ks2_dec: Aes::Dec,
41    h2: [u8; BLOCK_LENGTH],
42    l2: [u8; BLOCK_LENGTH],
43}
44
45/// CHCTR2 with AES-128 encryption (uses two AES-128 keys = 32 bytes total).
46#[allow(non_camel_case_types)]
47pub type Chctr2_128 = Chctr2<Aes128>;
48
49/// CHCTR2 with AES-256 encryption (uses two AES-256 keys = 64 bytes total).
50#[allow(non_camel_case_types)]
51pub type Chctr2_256 = Chctr2<Aes256>;
52
53impl<Aes: AesCipher> Chctr2<Aes> {
54    /// Total key length in bytes (two AES keys).
55    pub const KEY_LENGTH: usize = Aes::KEY_LEN * 2;
56
57    /// Single AES key length.
58    pub const SINGLE_KEY_LENGTH: usize = Aes::KEY_LEN;
59
60    /// AES block length in bytes (always 16).
61    pub const BLOCK_LENGTH: usize = BLOCK_LENGTH;
62
63    /// Minimum input length in bytes.
64    pub const MIN_INPUT_LENGTH: usize = BLOCK_LENGTH;
65
66    /// Initialize CHCTR2 from two separate keys.
67    pub fn new_split(key1: &[u8], key2: &[u8]) -> Self {
68        debug_assert_eq!(key1.len(), Aes::KEY_LEN);
69        debug_assert_eq!(key2.len(), Aes::KEY_LEN);
70
71        fn derive_hl<A: BlockCipherEncrypt>(ks: &A) -> ([u8; 16], [u8; 16]) {
72            let mut h_block = Array::clone_from_slice(&[0u8; 16]);
73            let mut l_block = Array::clone_from_slice(&{
74                let mut b = [0u8; 16];
75                b[0] = 1;
76                b
77            });
78            ks.encrypt_block(&mut h_block);
79            ks.encrypt_block(&mut l_block);
80            (
81                h_block.as_slice().try_into().unwrap(),
82                l_block.as_slice().try_into().unwrap(),
83            )
84        }
85
86        let ks1_enc = Aes::new(Array::from_slice(key1));
87        let ks1_dec = Aes::new_dec(key1);
88        let (h1, l1) = derive_hl(&ks1_enc);
89
90        let ks2_enc = Aes::new(Array::from_slice(key2));
91        let ks2_dec = Aes::new_dec(key2);
92        let (h2, l2) = derive_hl(&ks2_enc);
93
94        Self {
95            ks1_enc,
96            ks1_dec,
97            h1,
98            l1,
99            ks2_enc,
100            ks2_dec,
101            h2,
102            l2,
103        }
104    }
105
106    /// Encrypt plaintext to ciphertext using CHCTR2.
107    pub fn encrypt(
108        &self,
109        plaintext: &[u8],
110        tweak: &[u8],
111        ciphertext: &mut [u8],
112    ) -> Result<(), Error> {
113        self.chctr2(plaintext, tweak, ciphertext, Direction::Encrypt)
114    }
115
116    /// Decrypt ciphertext to plaintext using CHCTR2.
117    pub fn decrypt(
118        &self,
119        ciphertext: &[u8],
120        tweak: &[u8],
121        plaintext: &mut [u8],
122    ) -> Result<(), Error> {
123        self.chctr2(ciphertext, tweak, plaintext, Direction::Decrypt)
124    }
125
126    /// Optimized CHCTR2 implementation.
127    /// Structure: hash1-encrypt1-hash_{1,2}-encrypt2-hash2
128    fn chctr2(
129        &self,
130        src: &[u8],
131        tweak: &[u8],
132        dst: &mut [u8],
133        direction: Direction,
134    ) -> Result<(), Error> {
135        debug_assert_eq!(dst.len(), src.len());
136        if src.len() < BLOCK_LENGTH {
137            return Err(Error::InputTooShort);
138        }
139
140        let m0: [u8; BLOCK_LENGTH] = src[..BLOCK_LENGTH].try_into().unwrap();
141        let m_star = &src[BLOCK_LENGTH..];
142
143        let tweak_len_bits = tweak.len() * 8;
144        let tweak_len_encoded: u128 = if m_star.len() % BLOCK_LENGTH == 0 {
145            (2 * tweak_len_bits + 2) as u128
146        } else {
147            (2 * tweak_len_bits + 3) as u128
148        };
149        let len_block = Array::from(tweak_len_encoded.to_le_bytes());
150
151        let mut poly1 = Polyval::new(Array::from_slice(&self.h1));
152        poly1.update(&[len_block]);
153        poly1.update_padded(tweak);
154        let poly1_after_tweak = poly1.clone();
155
156        let mut poly2 = Polyval::new(Array::from_slice(&self.h2));
157        poly2.update(&[len_block]);
158        poly2.update_padded(tweak);
159        let poly2_after_tweak = poly2.clone();
160
161        match direction {
162            Direction::Encrypt => {
163                let z1 = absorb(&mut poly1, m_star);
164                let x1_0 = xor_blocks(&z1, &m0);
165
166                let y1_0: [u8; 16] = {
167                    let mut block = Array::clone_from_slice(&x1_0);
168                    self.ks1_enc.encrypt_block(&mut block);
169                    block.as_slice().try_into().unwrap()
170                };
171
172                let iv1 = xor_blocks_3(&x1_0, &y1_0, &self.l1);
173
174                let (_, r_slice) = dst.split_at_mut(BLOCK_LENGTH);
175                xctr(&self.ks1_enc, r_slice, m_star, &iv1);
176
177                let mut poly1 = poly1_after_tweak.clone();
178                let mut poly2 = poly2_after_tweak.clone();
179                let h1_r = absorb(&mut poly1, r_slice);
180                let h2_r = absorb(&mut poly2, r_slice);
181                let z1_2 = xor_blocks(&h1_r, &h2_r);
182
183                let x2_0 = xor_blocks(&y1_0, &z1_2);
184
185                let y2_0: [u8; 16] = {
186                    let mut block = Array::clone_from_slice(&x2_0);
187                    self.ks2_enc.encrypt_block(&mut block);
188                    block.as_slice().try_into().unwrap()
189                };
190
191                let iv2 = xor_blocks_3(&x2_0, &y2_0, &self.l2);
192
193                let c_star_src: Vec<u8> = r_slice.to_vec();
194                let (_, c_star) = dst.split_at_mut(BLOCK_LENGTH);
195                xctr(&self.ks2_enc, c_star, &c_star_src, &iv2);
196
197                let mut poly2 = poly2_after_tweak;
198                let z2 = absorb(&mut poly2, c_star);
199                dst[..BLOCK_LENGTH].copy_from_slice(&xor_blocks(&y2_0, &z2));
200            }
201            Direction::Decrypt => {
202                let c0: [u8; BLOCK_LENGTH] = src[..BLOCK_LENGTH].try_into().unwrap();
203                let c_star = &src[BLOCK_LENGTH..];
204
205                let z2 = absorb(&mut poly2, c_star);
206                let y2_0 = xor_blocks(&c0, &z2);
207
208                let x2_0: [u8; 16] = {
209                    let mut block = Array::clone_from_slice(&y2_0);
210                    self.ks2_dec.decrypt_block(&mut block);
211                    block.as_slice().try_into().unwrap()
212                };
213
214                let iv2 = xor_blocks_3(&x2_0, &y2_0, &self.l2);
215
216                let (_, r_slice) = dst.split_at_mut(BLOCK_LENGTH);
217                xctr(&self.ks2_enc, r_slice, c_star, &iv2);
218
219                let mut poly1 = poly1_after_tweak.clone();
220                let mut poly2 = poly2_after_tweak.clone();
221                let h1_r = absorb(&mut poly1, r_slice);
222                let h2_r = absorb(&mut poly2, r_slice);
223                let z1_2 = xor_blocks(&h1_r, &h2_r);
224
225                let y1_0 = xor_blocks(&x2_0, &z1_2);
226
227                let x1_0: [u8; 16] = {
228                    let mut block = Array::clone_from_slice(&y1_0);
229                    self.ks1_dec.decrypt_block(&mut block);
230                    block.as_slice().try_into().unwrap()
231                };
232
233                let iv1 = xor_blocks_3(&x1_0, &y1_0, &self.l1);
234
235                let r_copy: Vec<u8> = r_slice.to_vec();
236                let (_, m_star_out) = dst.split_at_mut(BLOCK_LENGTH);
237                xctr(&self.ks1_enc, m_star_out, &r_copy, &iv1);
238
239                let mut poly1 = poly1_after_tweak;
240                let z1 = absorb(&mut poly1, m_star_out);
241                dst[..BLOCK_LENGTH].copy_from_slice(&xor_blocks(&x1_0, &z1));
242            }
243        }
244
245        Ok(())
246    }
247}
248
249impl Chctr2_128 {
250    /// Initialize CHCTR2-128 from a combined key (K1 || K2).
251    pub fn new(key: &[u8; 32]) -> Self {
252        let key1: [u8; 16] = key[..16].try_into().unwrap();
253        let key2: [u8; 16] = key[16..].try_into().unwrap();
254        Self::new_split(&key1, &key2)
255    }
256}
257
258impl Chctr2_256 {
259    /// Initialize CHCTR2-256 from a combined key (K1 || K2).
260    pub fn new(key: &[u8; 64]) -> Self {
261        let key1: [u8; 32] = key[..32].try_into().unwrap();
262        let key2: [u8; 32] = key[32..].try_into().unwrap();
263        Self::new_split(&key1, &key2)
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270
271    #[test]
272    fn test_chctr2_128_roundtrip() {
273        let key = [0u8; 32];
274        let cipher = Chctr2_128::new(&key);
275
276        let plaintext = b"Hello, CHCTR2 World!";
277        let mut ciphertext = vec![0u8; plaintext.len()];
278        let mut decrypted = vec![0u8; plaintext.len()];
279
280        let tweak = b"test tweak";
281
282        cipher.encrypt(plaintext, tweak, &mut ciphertext).unwrap();
283        cipher.decrypt(&ciphertext, tweak, &mut decrypted).unwrap();
284
285        assert_eq!(plaintext.as_slice(), decrypted.as_slice());
286    }
287
288    #[test]
289    fn test_chctr2_128_roundtrip_nonzero_key() {
290        let key: [u8; 32] = core::array::from_fn(|i| (i + 1) as u8);
291        let cipher = Chctr2_128::new(&key);
292
293        let plaintext = b"Hello, CHCTR2 World!";
294        let mut ciphertext = vec![0u8; plaintext.len()];
295        let mut decrypted = vec![0u8; plaintext.len()];
296
297        let tweak = b"test tweak";
298
299        cipher.encrypt(plaintext, tweak, &mut ciphertext).unwrap();
300        assert_ne!(plaintext.as_slice(), ciphertext.as_slice());
301
302        cipher.decrypt(&ciphertext, tweak, &mut decrypted).unwrap();
303        assert_eq!(plaintext.as_slice(), decrypted.as_slice());
304    }
305
306    #[test]
307    fn test_chctr2_128_minimum_length() {
308        let key = [0u8; 32];
309        let cipher = Chctr2_128::new(&key);
310
311        let plaintext = [0x42u8; 16];
312        let mut ciphertext = [0u8; 16];
313        let mut decrypted = [0u8; 16];
314
315        cipher.encrypt(&plaintext, b"", &mut ciphertext).unwrap();
316        cipher.decrypt(&ciphertext, b"", &mut decrypted).unwrap();
317
318        assert_eq!(plaintext, decrypted);
319    }
320
321    #[test]
322    fn test_chctr2_128_input_too_short() {
323        let key = [0u8; 32];
324        let cipher = Chctr2_128::new(&key);
325
326        let plaintext = [0x42u8; 15];
327        let mut ciphertext = [0u8; 15];
328
329        assert_eq!(
330            cipher.encrypt(&plaintext, b"", &mut ciphertext),
331            Err(Error::InputTooShort)
332        );
333    }
334
335    #[test]
336    fn test_chctr2_128_different_tweaks() {
337        let key = [0u8; 32];
338        let cipher = Chctr2_128::new(&key);
339
340        let plaintext = [0x42u8; 32];
341        let mut ciphertext1 = [0u8; 32];
342        let mut ciphertext2 = [0u8; 32];
343
344        cipher
345            .encrypt(&plaintext, b"tweak1", &mut ciphertext1)
346            .unwrap();
347        cipher
348            .encrypt(&plaintext, b"tweak2", &mut ciphertext2)
349            .unwrap();
350
351        assert_ne!(ciphertext1, ciphertext2);
352    }
353
354    #[test]
355    fn test_chctr2_128_split_init() {
356        let key1 = [0x01u8; 16];
357        let key2 = [0x02u8; 16];
358        let cipher = Chctr2_128::new_split(&key1, &key2);
359
360        let plaintext = b"Test split key init";
361        let mut ciphertext = vec![0u8; plaintext.len()];
362        let mut decrypted = vec![0u8; plaintext.len()];
363
364        cipher
365            .encrypt(plaintext, b"tweak", &mut ciphertext)
366            .unwrap();
367        cipher
368            .decrypt(&ciphertext, b"tweak", &mut decrypted)
369            .unwrap();
370
371        assert_eq!(plaintext.as_slice(), decrypted.as_slice());
372    }
373
374    #[test]
375    fn test_chctr2_128_large_message() {
376        let key = [0u8; 32];
377        let cipher = Chctr2_128::new(&key);
378
379        let plaintext = [0xABu8; 1024];
380        let mut ciphertext = [0u8; 1024];
381        let mut decrypted = [0u8; 1024];
382
383        cipher
384            .encrypt(&plaintext, b"large tweak", &mut ciphertext)
385            .unwrap();
386        cipher
387            .decrypt(&ciphertext, b"large tweak", &mut decrypted)
388            .unwrap();
389
390        assert_eq!(plaintext.as_slice(), decrypted.as_slice());
391    }
392
393    #[test]
394    fn test_chctr2_256_roundtrip() {
395        let key = [0u8; 64];
396        let cipher = Chctr2_256::new(&key);
397
398        let plaintext = b"Hello, CHCTR2-256 World!";
399        let mut ciphertext = vec![0u8; plaintext.len()];
400        let mut decrypted = vec![0u8; plaintext.len()];
401
402        let tweak = b"test tweak 256";
403
404        cipher.encrypt(plaintext, tweak, &mut ciphertext).unwrap();
405        cipher.decrypt(&ciphertext, tweak, &mut decrypted).unwrap();
406
407        assert_eq!(plaintext.as_slice(), decrypted.as_slice());
408    }
409}