hctr2_rs/
common.rs

1#![allow(deprecated)]
2//! Common utilities shared across HCTR2/HCTR3 cipher implementations.
3
4#[allow(deprecated)]
5use aes::cipher::{Array, BlockCipherEncrypt};
6use polyval::{Polyval, universal_hash::UniversalHash};
7
8/// Unified error type for all HCTR cipher operations.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Error {
11    /// Input is shorter than the minimum required length.
12    InputTooShort,
13    /// Tweak is longer than the maximum allowed.
14    TweakTooLong,
15    /// A digit value is out of range for the radix.
16    InvalidDigit,
17}
18
19impl core::fmt::Display for Error {
20    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
21        match self {
22            Error::InputTooShort => write!(f, "input too short"),
23            Error::TweakTooLong => write!(f, "tweak too long"),
24            Error::InvalidDigit => write!(f, "digit out of range for radix"),
25        }
26    }
27}
28
29#[cfg(feature = "std")]
30impl std::error::Error for Error {}
31
32/// AES block length in bytes.
33pub const BLOCK_LENGTH: usize = 16;
34
35/// Direction of cipher operation.
36#[derive(Clone, Copy)]
37pub enum Direction {
38    Encrypt,
39    Decrypt,
40}
41
42/// Absorb message into Polyval with HCTR2/HCTR3-style padding.
43///
44/// Pads incomplete blocks with a single 1 bit followed by zeros.
45pub fn absorb(poly: &mut Polyval, msg: &[u8]) -> [u8; BLOCK_LENGTH] {
46    let full_blocks = msg.len() / BLOCK_LENGTH;
47    for i in 0..full_blocks {
48        let block = Array::clone_from_slice(&msg[i * BLOCK_LENGTH..(i + 1) * BLOCK_LENGTH]);
49        poly.update(&[block]);
50    }
51
52    let remainder = msg.len() % BLOCK_LENGTH;
53    if remainder > 0 {
54        let start = full_blocks * BLOCK_LENGTH;
55        let remaining = &msg[start..];
56
57        let mut padded_block = [0u8; BLOCK_LENGTH];
58        padded_block[..remaining.len()].copy_from_slice(remaining);
59        padded_block[remaining.len()] = 1;
60        poly.update(&[Array::clone_from_slice(&padded_block)]);
61    }
62
63    let mut hh = [0u8; BLOCK_LENGTH];
64    hh.copy_from_slice(poly.clone().finalize().as_slice());
65    hh
66}
67
68/// XCTR mode: counter-based stream cipher using AES.
69///
70/// This is a self-inverse operation (encryption and decryption are identical).
71pub fn xctr<Aes: BlockCipherEncrypt>(
72    ks_enc: &Aes,
73    dst: &mut [u8],
74    src: &[u8],
75    z: &[u8; BLOCK_LENGTH],
76) {
77    let mut counter: u64 = 1;
78    let mut i = 0;
79
80    while i + BLOCK_LENGTH <= src.len() {
81        let mut counter_bytes = [0u8; BLOCK_LENGTH];
82        counter_bytes[..8].copy_from_slice(&counter.to_le_bytes());
83
84        for j in 0..BLOCK_LENGTH {
85            counter_bytes[j] ^= z[j];
86        }
87
88        let mut block = Array::clone_from_slice(&counter_bytes);
89        ks_enc.encrypt_block(&mut block);
90
91        for j in 0..BLOCK_LENGTH {
92            dst[i + j] = src[i + j] ^ block[j];
93        }
94
95        counter += 1;
96        i += BLOCK_LENGTH;
97    }
98
99    let left = src.len() - i;
100    if left > 0 {
101        let mut counter_bytes = [0u8; BLOCK_LENGTH];
102        counter_bytes[..8].copy_from_slice(&counter.to_le_bytes());
103
104        for j in 0..BLOCK_LENGTH {
105            counter_bytes[j] ^= z[j];
106        }
107
108        let mut block = Array::clone_from_slice(&counter_bytes);
109        ks_enc.encrypt_block(&mut block);
110
111        for j in 0..left {
112            dst[i + j] = src[i + j] ^ block[j];
113        }
114    }
115}
116
117/// LFSR next state function for 128-bit state.
118///
119/// Uses primitive polynomial x^128 + x^7 + x^2 + x + 1 (Galois configuration).
120/// Implementation is constant-time to prevent timing side-channel attacks.
121#[inline]
122pub fn lfsr_next_128(state: &[u8; 16]) -> [u8; 16] {
123    let mut result = *state;
124
125    let msb = result[15] >> 7;
126    let mask = 0u8.wrapping_sub(msb);
127
128    let mut carry: u8 = 0;
129    for byte in result.iter_mut() {
130        let new_carry = (*byte & 0x80) >> 7;
131        *byte = (*byte << 1) | carry;
132        carry = new_carry;
133    }
134
135    result[0] ^= 0x87 & mask;
136
137    result
138}
139
140/// LFSR next state function for 256-bit state.
141///
142/// Uses primitive polynomial x^256 + x^254 + x^251 + x^246 + 1 (Galois configuration).
143/// Implementation is constant-time to prevent timing side-channel attacks.
144#[inline]
145#[allow(dead_code)]
146pub fn lfsr_next_256(state: &[u8; 32]) -> [u8; 32] {
147    let mut result = *state;
148
149    let msb = result[31] >> 7;
150    let mask = 0u8.wrapping_sub(msb);
151
152    let mut carry: u8 = 0;
153    for byte in result.iter_mut() {
154        let new_carry = (*byte & 0x80) >> 7;
155        *byte = (*byte << 1) | carry;
156        carry = new_carry;
157    }
158
159    result[0] ^= 0x01 & mask;
160    result[30] ^= 0x40 & mask;
161    result[31] ^= 0x08 & mask;
162    result[31] ^= 0x40 & mask;
163
164    result
165}
166
167/// XOR two 16-byte blocks, storing result in the first argument.
168#[inline]
169pub fn xor_block(dst: &mut [u8; BLOCK_LENGTH], src: &[u8; BLOCK_LENGTH]) {
170    for i in 0..BLOCK_LENGTH {
171        dst[i] ^= src[i];
172    }
173}
174
175/// XOR two 16-byte blocks, returning a new block.
176#[inline]
177pub fn xor_blocks(a: &[u8; BLOCK_LENGTH], b: &[u8; BLOCK_LENGTH]) -> [u8; BLOCK_LENGTH] {
178    let mut result = *a;
179    xor_block(&mut result, b);
180    result
181}
182
183/// XOR three 16-byte blocks, returning a new block.
184#[inline]
185pub fn xor_blocks_3(
186    a: &[u8; BLOCK_LENGTH],
187    b: &[u8; BLOCK_LENGTH],
188    c: &[u8; BLOCK_LENGTH],
189) -> [u8; BLOCK_LENGTH] {
190    let mut result = [0u8; BLOCK_LENGTH];
191    for i in 0..BLOCK_LENGTH {
192        result[i] = a[i] ^ b[i] ^ c[i];
193    }
194    result
195}
196
197/// ELK mode: Encrypted LFSR Keystream.
198///
199/// This function XORs the source with an encrypted LFSR keystream.
200/// Used by HCTR3 instead of XCTR.
201pub fn elk<Aes: BlockCipherEncrypt>(
202    ks: &Aes,
203    dst: &mut [u8],
204    src: &[u8],
205    seed: &[u8; BLOCK_LENGTH],
206) {
207    let mut lfsr_state = *seed;
208    let mut i = 0;
209
210    while i + BLOCK_LENGTH <= src.len() {
211        let mut block = Array::clone_from_slice(&lfsr_state);
212        ks.encrypt_block(&mut block);
213
214        for j in 0..BLOCK_LENGTH {
215            dst[i + j] = src[i + j] ^ block[j];
216        }
217
218        lfsr_state = lfsr_next_128(&lfsr_state);
219        i += BLOCK_LENGTH;
220    }
221
222    let left = src.len() - i;
223    if left > 0 {
224        let mut block = Array::clone_from_slice(&lfsr_state);
225        ks.encrypt_block(&mut block);
226
227        for j in 0..left {
228            dst[i + j] = src[i + j] ^ block[j];
229        }
230    }
231}