Skip to main content

dcrypt_algorithms/xof/shake/
mod.rs

1//! SHAKE extendable output functions
2//!
3//! This module implements the SHAKE family of extendable output functions (XOFs)
4//! as specified in FIPS PUB 202.
5//!
6//! These are distinct from the SHAKE implementations in the hash module,
7//! which provide fixed-output hash function interfaces. This module provides
8//! the proper XOF interface for variable-length output generation.
9
10use dcrypt_internal::zeroing::{
11    boxed_bytes_zeroed, Zeroize, ZeroizeOnDrop, Zeroizing, ZeroizingBytes,
12};
13
14use super::ExtendableOutputFunction;
15use crate::error::{validate, Error, Result};
16
17// Import security types from dcrypt-core
18use dcrypt_common::security::{barrier, EphemeralSecret, SecretBuffer, SecureZeroingType};
19
20// SHAKE constants
21const KECCAK_ROUNDS: usize = 24;
22const KECCAK_STATE_SIZE: usize = 25; // 5x5 of 64-bit words
23
24// SHAKE rates (in bytes): r = 1600 - 2*security_level
25const SHAKE128_RATE: usize = 168; // 1600 - 2*128 = 1600 - 256 = 1344 bits = 168 bytes
26const SHAKE256_RATE: usize = 136; // 1600 - 2*256 = 1600 - 512 = 1088 bits = 136 bytes
27
28// Round constants for Keccak
29const RC: [u64; KECCAK_ROUNDS] = [
30    0x0000000000000001,
31    0x0000000000008082,
32    0x800000000000808A,
33    0x8000000080008000,
34    0x000000000000808B,
35    0x0000000080000001,
36    0x8000000080008081,
37    0x8000000000008009,
38    0x000000000000008A,
39    0x0000000000000088,
40    0x0000000080008009,
41    0x000000008000000A,
42    0x000000008000808B,
43    0x800000000000008B,
44    0x8000000000008089,
45    0x8000000000008003,
46    0x8000000000008002,
47    0x8000000000000080,
48    0x000000000000800A,
49    0x800000008000000A,
50    0x8000000080008081,
51    0x8000000000008080,
52    0x0000000080000001,
53    0x8000000080008008,
54];
55
56// Rotation offsets
57const RHO: [u32; 24] = [
58    1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44,
59];
60
61// Mapping from index positions to x,y coordinates in the state array
62const PI: [usize; 24] = [
63    10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1,
64];
65
66// Helper struct for secure Keccak state operations
67#[derive(Clone)]
68struct SecureKeccakState {
69    state: SecretBuffer<200>, // 25 * 8 bytes
70}
71
72impl Zeroize for SecureKeccakState {
73    fn zeroize(&mut self) {
74        self.state.zeroize();
75    }
76}
77
78impl Drop for SecureKeccakState {
79    fn drop(&mut self) {
80        self.zeroize();
81    }
82}
83
84impl ZeroizeOnDrop for SecureKeccakState {}
85
86impl SecureKeccakState {
87    fn new() -> Self {
88        Self {
89            state: SecretBuffer::zeroed(),
90        }
91    }
92
93    fn from_u64_array(array: &[u64; KECCAK_STATE_SIZE]) -> Self {
94        let mut state = SecretBuffer::<200>::zeroed();
95        for (i, word) in array.iter().enumerate() {
96            for byte in 0..8 {
97                state.as_mut()[i * 8 + byte] = (word >> (byte * 8)) as u8;
98            }
99        }
100        Self { state }
101    }
102
103    fn to_u64_array(&self) -> Zeroizing<[u64; KECCAK_STATE_SIZE]> {
104        let mut array = Zeroizing::new([0u64; KECCAK_STATE_SIZE]);
105        let bytes = self.state.as_ref();
106        for (i, word) in array.iter_mut().enumerate() {
107            let start = i * 8;
108            for byte in 0..8 {
109                *word |= (bytes[start + byte] as u64) << (byte * 8);
110            }
111        }
112        array
113    }
114
115    fn apply_permutation(&mut self) {
116        let mut state_array = self.to_u64_array();
117        keccak_f1600(&mut state_array);
118        *self = Self::from_u64_array(&state_array);
119    }
120}
121
122impl SecureZeroingType for SecureKeccakState {
123    fn zeroed() -> Self {
124        Self::new()
125    }
126
127    fn secure_clone(&self) -> Self {
128        Self {
129            state: self.state.secure_clone(),
130        }
131    }
132}
133
134/// SHAKE-128 extendable output function with secure memory handling
135#[derive(Clone)]
136pub struct ShakeXof128 {
137    state: SecureKeccakState,
138    buffer: SecretBuffer<SHAKE128_RATE>,
139    buffer_idx: usize,
140    is_finalized: bool,
141    squeezing: bool,
142}
143
144impl Zeroize for ShakeXof128 {
145    fn zeroize(&mut self) {
146        self.state.zeroize();
147        self.buffer.zeroize();
148        self.buffer_idx.zeroize();
149        self.is_finalized = false;
150        self.squeezing = false;
151    }
152}
153
154impl Drop for ShakeXof128 {
155    fn drop(&mut self) {
156        self.zeroize();
157    }
158}
159
160impl ZeroizeOnDrop for ShakeXof128 {}
161
162/// SHAKE-256 extendable output function with secure memory handling
163#[derive(Clone)]
164pub struct ShakeXof256 {
165    state: SecureKeccakState,
166    buffer: SecretBuffer<SHAKE256_RATE>,
167    buffer_idx: usize,
168    is_finalized: bool,
169    squeezing: bool,
170}
171
172impl Zeroize for ShakeXof256 {
173    fn zeroize(&mut self) {
174        self.state.zeroize();
175        self.buffer.zeroize();
176        self.buffer_idx.zeroize();
177        self.is_finalized = false;
178        self.squeezing = false;
179    }
180}
181
182impl Drop for ShakeXof256 {
183    fn drop(&mut self) {
184        self.zeroize();
185    }
186}
187
188impl ZeroizeOnDrop for ShakeXof256 {}
189
190// Helper functions for Keccak permutation
191
192/// Performs a full Keccak-f[1600] permutation on the state
193fn keccak_f1600(state: &mut [u64; KECCAK_STATE_SIZE]) {
194    // Use EphemeralSecret for temporary arrays
195    for (_round, &rc) in RC.iter().enumerate().take(KECCAK_ROUNDS) {
196        // Theta step with secure temporary storage
197        let mut c = EphemeralSecret::new([0u64; 5]);
198        for x in 0..5 {
199            c.as_mut()[x] = state[x] ^ state[x + 5] ^ state[x + 10] ^ state[x + 15] ^ state[x + 20];
200        }
201
202        let mut d = EphemeralSecret::new([0u64; 5]);
203        for x in 0..5 {
204            d.as_mut()[x] = c.as_ref()[(x + 4) % 5] ^ c.as_ref()[(x + 1) % 5].rotate_left(1);
205        }
206
207        for y in 0..5 {
208            for x in 0..5 {
209                state[x + 5 * y] ^= d.as_ref()[x];
210            }
211        }
212
213        // Rho and Pi steps with secure temporary storage
214        let mut b = EphemeralSecret::new([0u64; KECCAK_STATE_SIZE]);
215        let mut x = 1;
216        let mut y = 0;
217        b.as_mut()[0] = state[0];
218
219        for i in 0..24 {
220            let idx = x + 5 * y;
221            b.as_mut()[PI[i]] = state[idx].rotate_left(RHO[i]);
222            let temp = y;
223            y = (2 * x + 3 * y) % 5;
224            x = temp;
225        }
226
227        // Chi step
228        for y in 0..5 {
229            for x in 0..5 {
230                let idx = x + 5 * y;
231                state[idx] = b.as_ref()[idx]
232                    ^ ((!b.as_ref()[(x + 1) % 5 + 5 * y]) & b.as_ref()[(x + 2) % 5 + 5 * y]);
233            }
234        }
235
236        // Iota step
237        state[0] ^= rc;
238    }
239
240    // Insert memory barrier after permutation
241    barrier::compiler_fence_seq_cst();
242}
243
244/// Absorbs data into the sponge state with secure handling
245fn keccak_absorb(state: &mut SecureKeccakState, data: &[u8], rate: usize) {
246    // Get state as u64 array for processing
247    let mut state_array = state.to_u64_array();
248
249    for (i, &byte) in data.iter().enumerate() {
250        let pos = i % rate;
251        let byte_idx = pos % 8;
252        let word_idx = pos / 8;
253        state_array[word_idx] ^= (byte as u64) << (8 * byte_idx);
254    }
255
256    if !data.is_empty() && data.len() % rate == 0 {
257        keccak_f1600(&mut state_array);
258    }
259
260    // Update secure state
261    *state = SecureKeccakState::from_u64_array(&state_array);
262}
263
264fn validate_bit_string(data: &[u8], bit_len: usize) -> Result<(usize, usize)> {
265    let rounded_len = bit_len
266        .checked_add(7)
267        .ok_or_else(|| Error::param("bit_length", "Bit length is too large"))?
268        / 8;
269    validate::length("bit-oriented SHAKE input", data.len(), rounded_len)?;
270
271    let partial_bits = bit_len % 8;
272    if partial_bits != 0 {
273        let unused_mask = (1u8 << (8 - partial_bits)) - 1;
274        validate::parameter(
275            data[rounded_len - 1] & unused_mask == 0,
276            "data",
277            "Unused low bits in the final byte must be zero",
278        )?;
279    }
280
281    Ok((bit_len / 8, partial_bits))
282}
283
284fn append_bit<const RATE: usize>(
285    state: &mut SecureKeccakState,
286    block: &mut SecretBuffer<RATE>,
287    bit_pos: &mut usize,
288    bit: u8,
289) {
290    block.as_mut()[*bit_pos / 8] ^= (bit & 1) << (*bit_pos % 8);
291    *bit_pos += 1;
292    if *bit_pos == RATE * 8 {
293        keccak_absorb(state, block.as_ref(), RATE);
294        block.zeroize();
295        *bit_pos = 0;
296    }
297}
298
299fn finalize_bit_string<const RATE: usize>(
300    state: &mut SecureKeccakState,
301    buffered: &SecretBuffer<RATE>,
302    buffer_idx: usize,
303    mut partial_byte: u8,
304    partial_bits: usize,
305) {
306    let mut block = SecretBuffer::<RATE>::zeroed();
307    block.as_mut()[..buffer_idx].copy_from_slice(&buffered.as_ref()[..buffer_idx]);
308    let mut bit_pos = buffer_idx * 8;
309
310    for i in 0..partial_bits {
311        append_bit(state, &mut block, &mut bit_pos, (partial_byte >> i) & 1);
312    }
313    partial_byte.zeroize();
314
315    // FIPS 202 SHAKE domain bits (1111) followed by the first bit of
316    // pad10*1 form the five-bit delimited suffix 0x1f.
317    for i in 0..5 {
318        append_bit(state, &mut block, &mut bit_pos, (0x1f >> i) & 1);
319    }
320
321    // Terminate the current rate block with the final bit of pad10*1.
322    block.as_mut()[RATE - 1] ^= 0x80;
323    keccak_absorb(state, block.as_ref(), RATE);
324}
325
326impl ShakeXof128 {
327    fn init() -> Self {
328        ShakeXof128 {
329            state: SecureKeccakState::new(),
330            buffer: SecretBuffer::zeroed(),
331            buffer_idx: 0,
332            is_finalized: false,
333            squeezing: false,
334        }
335    }
336
337    fn finalize_bits_internal(&mut self, partial_byte: u8, partial_bits: usize) -> Result<()> {
338        validate::parameter(
339            !self.is_finalized,
340            "state",
341            "SHAKE input has already been finalized",
342        )?;
343        validate::parameter(
344            !self.squeezing,
345            "state",
346            "SHAKE output is already being squeezed",
347        )?;
348        finalize_bit_string(
349            &mut self.state,
350            &self.buffer,
351            self.buffer_idx,
352            partial_byte,
353            partial_bits,
354        );
355        self.buffer.zeroize();
356        self.buffer_idx = 0;
357        self.is_finalized = true;
358        Ok(())
359    }
360
361    /// Generate SHAKE output for a bit-oriented input while preserving the
362    /// byte-oriented streaming API. Bits in a partial final input byte occupy
363    /// its most-significant positions, matching NIST ACVP representation.
364    #[doc(hidden)]
365    pub fn generate_bits(data: &[u8], bit_len: usize, output_len: usize) -> Result<ZeroizingBytes> {
366        validate::parameter(
367            output_len > 0,
368            "output_length",
369            "XOF output length must be greater than 0",
370        )?;
371        let (full_bytes, partial_bits) = validate_bit_string(data, bit_len)?;
372        let mut xof = Self::init();
373        xof.update(&data[..full_bytes])?;
374        let mut partial_byte = if partial_bits == 0 {
375            0
376        } else {
377            data[full_bytes] >> (8 - partial_bits)
378        };
379        xof.finalize_bits_internal(partial_byte, partial_bits)?;
380        partial_byte.zeroize();
381        xof.squeeze_into_vec(output_len)
382    }
383}
384
385impl ExtendableOutputFunction for ShakeXof128 {
386    fn new() -> Self {
387        Self::init()
388    }
389
390    fn update(&mut self, data: &[u8]) -> Result<()> {
391        if self.is_finalized {
392            return Err(Error::xof_finalized());
393        }
394        if self.squeezing {
395            return Err(Error::xof_squeezing());
396        }
397
398        let mut idx = 0;
399        if self.buffer_idx > 0 {
400            let to_copy = (SHAKE128_RATE - self.buffer_idx).min(data.len());
401            let buffer_slice =
402                &mut self.buffer.as_mut()[self.buffer_idx..self.buffer_idx + to_copy];
403            buffer_slice.copy_from_slice(&data[..to_copy]);
404            self.buffer_idx += to_copy;
405            idx = to_copy;
406
407            if self.buffer_idx == SHAKE128_RATE {
408                keccak_absorb(&mut self.state, self.buffer.as_ref(), SHAKE128_RATE);
409                self.buffer.zeroize();
410                self.buffer_idx = 0;
411            }
412        }
413
414        let remaining = data.len() - idx;
415        let full_blocks = remaining / SHAKE128_RATE;
416        for i in 0..full_blocks {
417            let start = idx + i * SHAKE128_RATE;
418            let block = &data[start..start + SHAKE128_RATE];
419
420            // Process directly without copying to buffer
421            keccak_absorb(&mut self.state, block, SHAKE128_RATE);
422        }
423        idx += full_blocks * SHAKE128_RATE;
424
425        if idx < data.len() {
426            let rem = data.len() - idx;
427            self.buffer.as_mut()[..rem].copy_from_slice(&data[idx..]);
428            self.buffer_idx = rem;
429        }
430
431        Ok(())
432    }
433
434    fn finalize(&mut self) -> Result<()> {
435        if self.is_finalized {
436            return Ok(());
437        }
438
439        // Use SecretBuffer for pad block
440        let mut pad_block = SecretBuffer::<SHAKE128_RATE>::zeroed();
441        pad_block.as_mut()[..self.buffer_idx]
442            .copy_from_slice(&self.buffer.as_ref()[..self.buffer_idx]);
443        pad_block.as_mut()[self.buffer_idx] ^= 0x1F;
444        pad_block.as_mut()[SHAKE128_RATE - 1] ^= 0x80;
445
446        self.buffer.zeroize();
447
448        keccak_absorb(&mut self.state, pad_block.as_ref(), SHAKE128_RATE);
449
450        self.is_finalized = true;
451        self.buffer_idx = 0;
452        Ok(())
453    }
454
455    fn squeeze(&mut self, output: &mut [u8]) -> Result<()> {
456        validate::parameter(
457            !output.is_empty(),
458            "output_length",
459            "Output buffer must not be empty",
460        )?;
461
462        if !self.is_finalized {
463            self.finalize()?;
464        }
465        self.squeezing = true;
466
467        let mut offset = 0;
468        let rate = SHAKE128_RATE;
469
470        while offset < output.len() {
471            if self.buffer_idx >= rate {
472                self.state.apply_permutation();
473                self.buffer_idx = 0;
474            }
475
476            if self.buffer_idx == 0 {
477                // Extract state into buffer
478                let state_array = self.state.to_u64_array();
479                let buffer_mut = self.buffer.as_mut();
480
481                for i in 0..(rate / 8) {
482                    let mut lane = state_array[i];
483                    for j in 0..8 {
484                        if i * 8 + j < rate {
485                            buffer_mut[i * 8 + j] = ((lane >> (8 * j)) & 0xFF) as u8;
486                        }
487                    }
488                    lane.zeroize();
489                }
490            }
491
492            let available = rate - self.buffer_idx;
493            let needed = output.len() - offset;
494            let to_copy = available.min(needed);
495
496            output[offset..offset + to_copy]
497                .copy_from_slice(&self.buffer.as_ref()[self.buffer_idx..self.buffer_idx + to_copy]);
498
499            offset += to_copy;
500            self.buffer_idx += to_copy;
501        }
502
503        // Memory barrier after squeeze operation
504        barrier::compiler_fence_seq_cst();
505        Ok(())
506    }
507
508    fn squeeze_into_vec(&mut self, len: usize) -> Result<ZeroizingBytes> {
509        validate::parameter(
510            len > 0,
511            "output_length",
512            "Output length must be greater than 0",
513        )?;
514
515        let mut v = Zeroizing::new(boxed_bytes_zeroed(len));
516        self.squeeze(&mut v)?;
517        Ok(v)
518    }
519
520    fn reset(&mut self) -> Result<()> {
521        *self = Self::new();
522        Ok(())
523    }
524
525    fn security_level() -> usize {
526        128
527    }
528}
529
530impl ShakeXof256 {
531    fn init() -> Self {
532        ShakeXof256 {
533            state: SecureKeccakState::new(),
534            buffer: SecretBuffer::zeroed(),
535            buffer_idx: 0,
536            is_finalized: false,
537            squeezing: false,
538        }
539    }
540
541    fn finalize_bits_internal(&mut self, partial_byte: u8, partial_bits: usize) -> Result<()> {
542        validate::parameter(
543            !self.is_finalized,
544            "state",
545            "SHAKE input has already been finalized",
546        )?;
547        validate::parameter(
548            !self.squeezing,
549            "state",
550            "SHAKE output is already being squeezed",
551        )?;
552        finalize_bit_string(
553            &mut self.state,
554            &self.buffer,
555            self.buffer_idx,
556            partial_byte,
557            partial_bits,
558        );
559        self.buffer.zeroize();
560        self.buffer_idx = 0;
561        self.is_finalized = true;
562        Ok(())
563    }
564
565    /// Generate SHAKE output for a bit-oriented input while preserving the
566    /// byte-oriented streaming API. Bits in a partial final input byte occupy
567    /// its most-significant positions, matching NIST ACVP representation.
568    #[doc(hidden)]
569    pub fn generate_bits(data: &[u8], bit_len: usize, output_len: usize) -> Result<ZeroizingBytes> {
570        validate::parameter(
571            output_len > 0,
572            "output_length",
573            "XOF output length must be greater than 0",
574        )?;
575        let (full_bytes, partial_bits) = validate_bit_string(data, bit_len)?;
576        let mut xof = Self::init();
577        xof.update(&data[..full_bytes])?;
578        let mut partial_byte = if partial_bits == 0 {
579            0
580        } else {
581            data[full_bytes] >> (8 - partial_bits)
582        };
583        xof.finalize_bits_internal(partial_byte, partial_bits)?;
584        partial_byte.zeroize();
585        xof.squeeze_into_vec(output_len)
586    }
587}
588
589impl ExtendableOutputFunction for ShakeXof256 {
590    fn new() -> Self {
591        Self::init()
592    }
593
594    fn update(&mut self, data: &[u8]) -> Result<()> {
595        if self.is_finalized {
596            return Err(Error::xof_finalized());
597        }
598        if self.squeezing {
599            return Err(Error::xof_squeezing());
600        }
601
602        let mut idx = 0;
603        if self.buffer_idx > 0 {
604            let to_copy = (SHAKE256_RATE - self.buffer_idx).min(data.len());
605            let buffer_slice =
606                &mut self.buffer.as_mut()[self.buffer_idx..self.buffer_idx + to_copy];
607            buffer_slice.copy_from_slice(&data[..to_copy]);
608            self.buffer_idx += to_copy;
609            idx = to_copy;
610
611            if self.buffer_idx == SHAKE256_RATE {
612                keccak_absorb(&mut self.state, self.buffer.as_ref(), SHAKE256_RATE);
613                self.buffer.zeroize();
614                self.buffer_idx = 0;
615            }
616        }
617
618        let remaining = data.len() - idx;
619        let full_blocks = remaining / SHAKE256_RATE;
620        for i in 0..full_blocks {
621            let start = idx + i * SHAKE256_RATE;
622            let block = &data[start..start + SHAKE256_RATE];
623
624            // Process directly without copying to buffer
625            keccak_absorb(&mut self.state, block, SHAKE256_RATE);
626        }
627        idx += full_blocks * SHAKE256_RATE;
628
629        if idx < data.len() {
630            let rem = data.len() - idx;
631            self.buffer.as_mut()[..rem].copy_from_slice(&data[idx..]);
632            self.buffer_idx = rem;
633        }
634
635        Ok(())
636    }
637
638    fn finalize(&mut self) -> Result<()> {
639        if self.is_finalized {
640            return Ok(());
641        }
642
643        // Use SecretBuffer for pad block
644        let mut pad_block = SecretBuffer::<SHAKE256_RATE>::zeroed();
645        pad_block.as_mut()[..self.buffer_idx]
646            .copy_from_slice(&self.buffer.as_ref()[..self.buffer_idx]);
647        pad_block.as_mut()[self.buffer_idx] ^= 0x1F;
648        pad_block.as_mut()[SHAKE256_RATE - 1] ^= 0x80;
649
650        self.buffer.zeroize();
651
652        keccak_absorb(&mut self.state, pad_block.as_ref(), SHAKE256_RATE);
653
654        self.is_finalized = true;
655        self.buffer_idx = 0;
656        Ok(())
657    }
658
659    fn squeeze(&mut self, output: &mut [u8]) -> Result<()> {
660        validate::parameter(
661            !output.is_empty(),
662            "output_length",
663            "Output buffer must not be empty",
664        )?;
665
666        if !self.is_finalized {
667            self.finalize()?;
668        }
669        self.squeezing = true;
670
671        let mut offset = 0;
672        let rate = SHAKE256_RATE;
673
674        while offset < output.len() {
675            if self.buffer_idx >= rate {
676                self.state.apply_permutation();
677                self.buffer_idx = 0;
678            }
679
680            if self.buffer_idx == 0 {
681                // Extract state into buffer
682                let state_array = self.state.to_u64_array();
683                let buffer_mut = self.buffer.as_mut();
684
685                for i in 0..(rate / 8) {
686                    let mut lane = state_array[i];
687                    for j in 0..8 {
688                        if i * 8 + j < rate {
689                            buffer_mut[i * 8 + j] = ((lane >> (8 * j)) & 0xFF) as u8;
690                        }
691                    }
692                    lane.zeroize();
693                }
694            }
695
696            let available = rate - self.buffer_idx;
697            let needed = output.len() - offset;
698            let to_copy = available.min(needed);
699
700            output[offset..offset + to_copy]
701                .copy_from_slice(&self.buffer.as_ref()[self.buffer_idx..self.buffer_idx + to_copy]);
702
703            offset += to_copy;
704            self.buffer_idx += to_copy;
705        }
706
707        // Memory barrier after squeeze operation
708        barrier::compiler_fence_seq_cst();
709        Ok(())
710    }
711
712    fn squeeze_into_vec(&mut self, len: usize) -> Result<ZeroizingBytes> {
713        validate::parameter(
714            len > 0,
715            "output_length",
716            "Output length must be greater than 0",
717        )?;
718
719        let mut v = Zeroizing::new(boxed_bytes_zeroed(len));
720        self.squeeze(&mut v)?;
721        Ok(v)
722    }
723
724    fn reset(&mut self) -> Result<()> {
725        *self = Self::new();
726        Ok(())
727    }
728
729    fn security_level() -> usize {
730        256
731    }
732}
733
734#[cfg(test)]
735mod tests;