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