Skip to main content

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