hctr2_rs/
hctr3.rs

1#![allow(deprecated)]
2//! HCTR3 (Hash-CTR-Hash version 3) length-preserving wide-block tweakable cipher.
3//!
4//! HCTR3 is an improved version of HCTR2 with enhanced security properties.
5//! Like HCTR2, it requires no nonce or authentication tag.
6//!
7//! Construction differences from HCTR2:
8//! - Two-key construction (encryption key + derived authentication key)
9//! - SHA-256 hashing of tweaks for domain separation
10//! - ELK mode (Encrypted LFSR Keystream) instead of XCTR
11//! - Constant-time LFSR implementation
12//!
13//! Security properties:
14//! - Ciphertext length equals plaintext length (no expansion)
15//! - Stronger security bounds than HCTR2
16//! - Requires unique (key, tweak) pairs for security
17//! - No authentication - consider AEAD if integrity protection is needed
18//! - Minimum message length: 16 bytes (one AES block)
19
20#[allow(deprecated)]
21use aes::cipher::{Array, BlockCipherDecrypt, KeyInit};
22use aes::{Aes128, Aes256};
23use polyval::{Polyval, universal_hash::UniversalHash};
24use sha2::{Digest, Sha256};
25
26use crate::common::{BLOCK_LENGTH, Direction, Error, absorb, elk, xor_blocks, xor_blocks_3};
27use crate::hctr2::AesCipher;
28
29// Keep the old error type as an alias for backwards compatibility
30#[allow(non_camel_case_types)]
31#[deprecated(note = "Use common::Error instead")]
32pub type Hctr3Error = Error;
33
34/// Generic HCTR3 cipher parameterized by AES key size.
35pub struct Hctr3<Aes: AesCipher> {
36    ks_enc: Aes,
37    ks_dec: Aes::Dec,
38    ke_enc: Aes,
39    h: [u8; BLOCK_LENGTH],
40    l: [u8; BLOCK_LENGTH],
41}
42
43/// HCTR3 with AES-128 encryption and SHA-256 tweak hashing.
44#[allow(non_camel_case_types)]
45pub type Hctr3_128 = Hctr3<Aes128>;
46
47/// HCTR3 with AES-256 encryption and SHA-256 tweak hashing.
48#[allow(non_camel_case_types)]
49pub type Hctr3_256 = Hctr3<Aes256>;
50
51impl<Aes: AesCipher> Hctr3<Aes> {
52    /// Encryption key length in bytes.
53    pub const KEY_LENGTH: usize = Aes::KEY_LEN;
54
55    /// AES block length in bytes (always 16).
56    pub const BLOCK_LENGTH: usize = BLOCK_LENGTH;
57
58    /// Minimum input length in bytes.
59    pub const MIN_INPUT_LENGTH: usize = BLOCK_LENGTH;
60
61    /// Initialize HCTR3 cipher state from an encryption key.
62    ///
63    /// Derives a secondary authentication key (Ke) from the encryption key for the two-key construction.
64    pub fn new(key: &[u8]) -> Self {
65        debug_assert_eq!(key.len(), Aes::KEY_LEN);
66
67        let ks_enc = Aes::new(Array::from_slice(key));
68        let ks_dec = Aes::new_dec(key);
69
70        let ke_key: Vec<u8> = if Aes::KEY_LEN <= 16 {
71            let mut ke_block = Array::clone_from_slice(&[0u8; 16]);
72            ks_enc.encrypt_block(&mut ke_block);
73            ke_block[..Aes::KEY_LEN].to_vec()
74        } else {
75            let mut ke_block0 = Array::clone_from_slice(&[0u8; 16]);
76            let mut ke_block1 = Array::clone_from_slice(&[0x01u8; 16]);
77            ks_enc.encrypt_block(&mut ke_block0);
78            ks_enc.encrypt_block(&mut ke_block1);
79            let mut ke = vec![0u8; Aes::KEY_LEN];
80            ke[..16].copy_from_slice(ke_block0.as_slice());
81            ke[16..].copy_from_slice(&ke_block1.as_slice()[..(Aes::KEY_LEN - 16)]);
82            ke
83        };
84
85        let ke_enc = Aes::new(Array::from_slice(&ke_key));
86
87        let mut h_block = Array::clone_from_slice(&[0u8; 16]);
88        let mut l_block = Array::clone_from_slice(&{
89            let mut b = [0u8; 16];
90            b[15] = 1;
91            b
92        });
93        ke_enc.encrypt_block(&mut h_block);
94        ke_enc.encrypt_block(&mut l_block);
95
96        let h: [u8; 16] = h_block.as_slice().try_into().unwrap();
97        let l: [u8; 16] = l_block.as_slice().try_into().unwrap();
98        Self {
99            ks_enc,
100            ks_dec,
101            ke_enc,
102            h,
103            l,
104        }
105    }
106
107    /// Encrypt plaintext to ciphertext using HCTR3.
108    pub fn encrypt(
109        &self,
110        plaintext: &[u8],
111        tweak: &[u8],
112        ciphertext: &mut [u8],
113    ) -> Result<(), Error> {
114        self.hctr3(plaintext, tweak, ciphertext, Direction::Encrypt)
115    }
116
117    /// Decrypt ciphertext to plaintext using HCTR3.
118    pub fn decrypt(
119        &self,
120        ciphertext: &[u8],
121        tweak: &[u8],
122        plaintext: &mut [u8],
123    ) -> Result<(), Error> {
124        self.hctr3(ciphertext, tweak, plaintext, Direction::Decrypt)
125    }
126
127    fn hctr3(
128        &self,
129        src: &[u8],
130        tweak: &[u8],
131        dst: &mut [u8],
132        direction: Direction,
133    ) -> Result<(), Error> {
134        debug_assert_eq!(dst.len(), src.len());
135        if src.len() < BLOCK_LENGTH {
136            return Err(Error::InputTooShort);
137        }
138
139        let m: [u8; BLOCK_LENGTH] = src[..BLOCK_LENGTH].try_into().unwrap();
140        let n = &src[BLOCK_LENGTH..];
141
142        let mut hasher = Sha256::new();
143        hasher.update(tweak);
144        let hash_out = hasher.finalize();
145        let t: [u8; BLOCK_LENGTH] = hash_out[..BLOCK_LENGTH].try_into().unwrap();
146
147        let tweak_len_bits = tweak.len() * 8;
148        let tweak_len_encoded: u128 = if n.len() % BLOCK_LENGTH == 0 {
149            (2 * tweak_len_bits + 2) as u128
150        } else {
151            (2 * tweak_len_bits + 3) as u128
152        };
153
154        let mut poly = Polyval::new(Array::from_slice(&self.h));
155        poly.update(&[Array::from(tweak_len_encoded.to_le_bytes())]);
156        poly.update(&[Array::from(t)]);
157        let poly_after_tweak = poly.clone();
158
159        let hh = absorb(&mut poly, n);
160        let mm = xor_blocks(&hh, &m);
161
162        let uu: [u8; BLOCK_LENGTH] = match direction {
163            Direction::Encrypt => {
164                let mut block = Array::clone_from_slice(&mm);
165                self.ks_enc.encrypt_block(&mut block);
166                block.as_slice().try_into().unwrap()
167            }
168            Direction::Decrypt => {
169                let mut block = Array::clone_from_slice(&mm);
170                self.ks_dec.decrypt_block(&mut block);
171                block.as_slice().try_into().unwrap()
172            }
173        };
174
175        let s = xor_blocks_3(&mm, &uu, &self.l);
176        let (u, v) = dst.split_at_mut(BLOCK_LENGTH);
177        elk(&self.ke_enc, v, n, &s);
178
179        let mut poly = poly_after_tweak;
180        let hh2 = absorb(&mut poly, v);
181        u.copy_from_slice(&xor_blocks(&uu, &hh2));
182
183        Ok(())
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use crate::common::lfsr_next_128;
191
192    #[test]
193    fn test_hctr3_128_roundtrip() {
194        let key = [0u8; 16];
195        let cipher = Hctr3_128::new(&key);
196
197        let plaintext = b"Hello, HCTR3 World!";
198        let mut ciphertext = vec![0u8; plaintext.len()];
199        let mut decrypted = vec![0u8; plaintext.len()];
200
201        let tweak = b"test tweak";
202
203        cipher.encrypt(plaintext, tweak, &mut ciphertext).unwrap();
204        cipher.decrypt(&ciphertext, tweak, &mut decrypted).unwrap();
205
206        assert_eq!(plaintext.as_slice(), decrypted.as_slice());
207    }
208
209    #[test]
210    fn test_hctr3_128_roundtrip_nonzero_key() {
211        let key = [
212            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
213            0x0f, 0x10,
214        ];
215        let cipher = Hctr3_128::new(&key);
216
217        let plaintext = b"Hello, HCTR3 World!";
218        let mut ciphertext = vec![0u8; plaintext.len()];
219        let mut decrypted = vec![0u8; plaintext.len()];
220
221        let tweak = b"test tweak";
222
223        cipher.encrypt(plaintext, tweak, &mut ciphertext).unwrap();
224        assert_ne!(plaintext.as_slice(), ciphertext.as_slice());
225
226        cipher.decrypt(&ciphertext, tweak, &mut decrypted).unwrap();
227        assert_eq!(plaintext.as_slice(), decrypted.as_slice());
228    }
229
230    #[test]
231    fn test_hctr3_128_minimum_length() {
232        let key = [0u8; 16];
233        let cipher = Hctr3_128::new(&key);
234
235        let plaintext = [0x42u8; 16];
236        let mut ciphertext = [0u8; 16];
237        let mut decrypted = [0u8; 16];
238
239        cipher.encrypt(&plaintext, b"", &mut ciphertext).unwrap();
240        cipher.decrypt(&ciphertext, b"", &mut decrypted).unwrap();
241
242        assert_eq!(plaintext, decrypted);
243    }
244
245    #[test]
246    fn test_hctr3_128_input_too_short() {
247        let key = [0u8; 16];
248        let cipher = Hctr3_128::new(&key);
249
250        let plaintext = [0x42u8; 15];
251        let mut ciphertext = [0u8; 15];
252
253        assert_eq!(
254            cipher.encrypt(&plaintext, b"", &mut ciphertext),
255            Err(Error::InputTooShort)
256        );
257    }
258
259    #[test]
260    fn test_hctr3_128_different_tweaks() {
261        let key = [0u8; 16];
262        let cipher = Hctr3_128::new(&key);
263
264        let plaintext = [0x42u8; 32];
265        let mut ciphertext1 = [0u8; 32];
266        let mut ciphertext2 = [0u8; 32];
267
268        cipher
269            .encrypt(&plaintext, b"tweak1", &mut ciphertext1)
270            .unwrap();
271        cipher
272            .encrypt(&plaintext, b"tweak2", &mut ciphertext2)
273            .unwrap();
274
275        assert_ne!(ciphertext1, ciphertext2);
276    }
277
278    #[test]
279    fn test_hctr3_128_large_message() {
280        let key = [0u8; 16];
281        let cipher = Hctr3_128::new(&key);
282
283        let plaintext = [0xABu8; 1024];
284        let mut ciphertext = [0u8; 1024];
285        let mut decrypted = [0u8; 1024];
286
287        cipher
288            .encrypt(&plaintext, b"large tweak", &mut ciphertext)
289            .unwrap();
290        cipher
291            .decrypt(&ciphertext, b"large tweak", &mut decrypted)
292            .unwrap();
293
294        assert_eq!(plaintext.as_slice(), decrypted.as_slice());
295    }
296
297    #[test]
298    fn test_hctr3_256_roundtrip() {
299        let key = [0u8; 32];
300        let cipher = Hctr3_256::new(&key);
301
302        let plaintext = b"Hello, HCTR3-256 World!";
303        let mut ciphertext = vec![0u8; plaintext.len()];
304        let mut decrypted = vec![0u8; plaintext.len()];
305
306        let tweak = b"test tweak 256";
307
308        cipher.encrypt(plaintext, tweak, &mut ciphertext).unwrap();
309        cipher.decrypt(&ciphertext, tweak, &mut decrypted).unwrap();
310
311        assert_eq!(plaintext.as_slice(), decrypted.as_slice());
312    }
313
314    #[test]
315    fn test_lfsr_128_produces_unique_states() {
316        let initial = [0x01u8; 16];
317        let mut state = initial;
318        let mut seen = std::collections::HashSet::new();
319        seen.insert(state);
320
321        for _ in 0..1000 {
322            state = lfsr_next_128(&state);
323            assert!(seen.insert(state), "LFSR produced duplicate state");
324        }
325    }
326}