Skip to main content

cryptoxide/hashing/
sha3.rs

1//! An implementation of the SHA-3 cryptographic hash algorithms.
2//!
3//! There are 6 standard algorithms specified in the SHA-3 standard:
4//!
5//!  * `SHA3-224`
6//!  * `SHA3-256`
7//!  * `SHA3-384`
8//!  * `SHA3-512`
9//!
10//! Based on an [implementation by Sébastien Martini](https://github.com/seb-m/crypto.rs/blob/master/src/sha3.rs)
11//!
12//! # Examples
13//!
14//! An example of using `SHA3-256` is:
15//!
16//! ```rust
17//! use cryptoxide::hashing::sha3;
18//!
19//! // create a SHA3-256 context
20//! let mut context = sha3::Sha3_256::new();
21//!
22//! // append input and get output digest
23//! let out : [u8; 32] = context.update(b"abc").finalize();
24//!
25//! ```
26
27use alloc::vec;
28use core::cmp;
29
30use crate::cryptoutil::{read_u64v_le, write_u64v_le, zero};
31
32pub(super) const B: usize = 200;
33const NROUNDS: usize = 24;
34const RC: [u64; 24] = [
35    0x0000000000000001,
36    0x0000000000008082,
37    0x800000000000808a,
38    0x8000000080008000,
39    0x000000000000808b,
40    0x0000000080000001,
41    0x8000000080008081,
42    0x8000000000008009,
43    0x000000000000008a,
44    0x0000000000000088,
45    0x0000000080008009,
46    0x000000008000000a,
47    0x000000008000808b,
48    0x800000000000008b,
49    0x8000000000008089,
50    0x8000000000008003,
51    0x8000000000008002,
52    0x8000000000000080,
53    0x000000000000800a,
54    0x800000008000000a,
55    0x8000000080008081,
56    0x8000000000008080,
57    0x0000000080000001,
58    0x8000000080008008,
59];
60const ROTC: [u32; 24] = [
61    1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44,
62];
63const PIL: [usize; 24] = [
64    10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1,
65];
66const M5: [usize; 10] = [0, 1, 2, 3, 4, 0, 1, 2, 3, 4];
67
68// Code based on Keccak-compact64.c from ref implementation.
69#[allow(clippy::needless_range_loop)]
70fn keccak_f(state: &mut [u8; B]) {
71    let mut s: [u64; 25] = [0; 25];
72    let mut t: [u64; 1] = [0; 1];
73    let mut c: [u64; 5] = [0; 5];
74
75    read_u64v_le(&mut s, state);
76
77    for round in 0..NROUNDS {
78        // Theta
79        for x in 0..5 {
80            c[x] = s[x] ^ s[5 + x] ^ s[10 + x] ^ s[15 + x] ^ s[20 + x];
81        }
82        for x in 0..5 {
83            t[0] = c[M5[x + 4]] ^ c[M5[x + 1]].rotate_left(1);
84            for y in 0..5 {
85                s[y * 5 + x] ^= t[0];
86            }
87        }
88
89        // Rho Pi
90        t[0] = s[1];
91        for x in 0..24 {
92            c[0] = s[PIL[x]];
93            s[PIL[x]] = t[0].rotate_left(ROTC[x]);
94            t[0] = c[0];
95        }
96
97        // Chi
98        for y in 0..5 {
99            for x in 0..5 {
100                c[x] = s[y * 5 + x];
101            }
102            for x in 0..5 {
103                s[y * 5 + x] = c[x] ^ (!c[M5[x + 1]] & c[M5[x + 2]]);
104            }
105        }
106
107        // Iota
108        s[0] ^= RC[round];
109    }
110
111    write_u64v_le(state, &s);
112}
113
114/// Engine for Keccak implementation where
115/// DSLEN = 0 (Keccak), 2 (SHA-3), 4 (Shake)
116/// DIGESTLEN = size in bytes of the digest (28, 32, 48, 64)
117#[derive(Clone)]
118pub(super) struct Engine<const DIGESTLEN: usize, const DSLEN: usize> {
119    state: [u8; B],    // B bytes
120    can_absorb: bool,  // Can absorb
121    can_squeeze: bool, // Can squeeze
122    offset: usize,     // Enqueued bytes in state for absorb phase
123                       // Squeeze offset for squeeze phase
124}
125
126impl<const DIGESTLEN: usize, const DSLEN: usize> Engine<DIGESTLEN, DSLEN> {
127    //pub const CAPACITY: usize = DIGESTLEN * 2;
128
129    fn rate(&self) -> usize {
130        B - (DIGESTLEN * 2)
131    }
132
133    /// New SHA-3 instanciated from specified SHA-3 `mode`.
134    pub const fn new() -> Self {
135        Self {
136            state: [0; B],
137            can_absorb: true,
138            can_squeeze: true,
139            offset: 0,
140        }
141    }
142
143    pub(super) fn finalize(&mut self) {
144        assert!(self.can_absorb);
145
146        fn set_domain_sep(out_len: usize, buf: &mut [u8]) {
147            assert!(!buf.is_empty());
148            if out_len != 0 {
149                // 01...
150                buf[0] &= 0xfe;
151                buf[0] |= 0x2;
152            } else {
153                // 1111...
154                buf[0] |= 0xf;
155            }
156        }
157
158        // All parameters are expected to be in bits.
159        fn pad_len<const DSLEN: usize>(offset: usize, rate: usize) -> usize {
160            assert!(rate % 8 == 0 && offset % 8 == 0);
161            let r: i64 = rate as i64;
162            let m: i64 = (offset + DSLEN) as i64;
163            let zeros = (((-m - 2) + 2 * r) % r) as usize;
164            assert!((m as usize + zeros + 2) % 8 == 0);
165            (DSLEN + zeros + 2) / 8
166        }
167
168        fn set_pad<const DSLEN: usize>(buf: &mut [u8]) {
169            //assert!(buf.len() as f32 >= ((offset + 2) as f32 / 8.0).ceil());
170            let offset = DSLEN;
171            let s = offset / 8;
172            let buflen = buf.len();
173            buf[s] |= 1 << (offset % 8);
174            for i in (offset % 8) + 1..8 {
175                buf[s] &= !(1 << i);
176            }
177            for b in buf[s + 1..].iter_mut() {
178                *b = 0;
179            }
180            buf[buflen - 1] |= 0x80;
181        }
182
183        let p_len = pad_len::<DSLEN>(self.offset * 8, self.rate() * 8);
184
185        let mut p = vec::from_elem(0, p_len);
186
187        if DSLEN != 0 {
188            set_domain_sep(DIGESTLEN * 8, &mut p);
189        }
190
191        set_pad::<DSLEN>(&mut p);
192
193        self.process(&p);
194        self.can_absorb = false;
195    }
196
197    pub(super) fn process(&mut self, data: &[u8]) {
198        if !self.can_absorb {
199            panic!("Invalid state, absorb phase already finalized.");
200        }
201
202        let r = self.rate();
203        assert!(self.offset < r);
204
205        let in_len = data.len();
206        let mut in_pos: usize = 0;
207
208        // Absorb
209        while in_pos < in_len {
210            let offset = self.offset;
211            let nread = cmp::min(r - offset, in_len - in_pos);
212            for i in 0..nread {
213                self.state[offset + i] ^= data[in_pos + i];
214            }
215            in_pos += nread;
216
217            if offset + nread != r {
218                self.offset += nread;
219                break;
220            }
221
222            self.offset = 0;
223            keccak_f(&mut self.state);
224        }
225    }
226
227    pub(super) fn reset(&mut self) {
228        self.can_absorb = true;
229        self.can_squeeze = true;
230        self.offset = 0;
231        zero(&mut self.state);
232    }
233
234    pub(super) fn output(&mut self, out: &mut [u8]) {
235        if !self.can_squeeze {
236            panic!("Nothing left to squeeze.");
237        }
238
239        if self.can_absorb {
240            self.finalize();
241        }
242
243        let r = self.rate();
244        if DIGESTLEN != 0 {
245            assert!(self.offset < DIGESTLEN);
246        } else {
247            // FIXME: only for SHAKE
248            assert!(self.offset < r);
249        }
250
251        let in_len = out.len();
252        let mut in_pos: usize = 0;
253
254        // Squeeze
255        while in_pos < in_len {
256            let offset = self.offset % r;
257            let mut nread = cmp::min(r - offset, in_len - in_pos);
258            if DIGESTLEN != 0 {
259                nread = cmp::min(nread, DIGESTLEN - self.offset);
260            }
261
262            out[in_pos..(nread + in_pos)].copy_from_slice(&self.state[offset..(nread + offset)]);
263            in_pos += nread;
264
265            if offset + nread != r {
266                self.offset += nread;
267                break;
268            }
269
270            if DIGESTLEN == 0 {
271                self.offset = 0;
272            } else {
273                self.offset += nread;
274            }
275
276            keccak_f(&mut self.state);
277        }
278
279        if DIGESTLEN != 0 && DIGESTLEN == self.offset {
280            self.can_squeeze = false;
281        }
282    }
283}
284
285/*
286/// New SHAKE-128 instance.
287pub fn shake_128() -> Sha3 {
288    Sha3::new(Sha3Mode::Shake128)
289}
290
291/// New SHAKE-256 instance.
292pub fn shake_256() -> Sha3 {
293    Sha3::new(Sha3Mode::Shake256)
294}
295*/
296macro_rules! sha3_impl {
297    ($C: ident, $context:ident, $digestlength:literal, $doc:expr) => {
298        #[doc=$doc]
299        #[doc = " Algorithm"]
300        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
301        pub struct $C;
302
303        impl $C {
304            /// Output of the hashing algorithm in bits
305            pub const OUTPUT_BITS: usize = $digestlength * 8;
306            /// The block size in bytes of the algorithm, which is the number of bytes the algorithm typically buffer
307            /// before calling its compression function
308            pub const BLOCK_BYTES: usize = B - ($digestlength * 2);
309
310            /// Create a new context for this algorithm
311            pub fn new() -> $context {
312                $context::new()
313            }
314        }
315
316        #[doc=$doc]
317        #[doc = " Context"]
318        #[derive(Clone)]
319        pub struct $context(Engine<$digestlength, 2>);
320
321        impl $context {
322            /// Create a new SHA3 Context
323            pub const fn new() -> Self {
324                Self(Engine::new())
325            }
326
327            /// Update in-place the hashing state by adding the input bytes slice into the state
328            ///
329            /// For the immutable version see [`update`]
330            pub fn update_mut(&mut self, data: &[u8]) {
331                self.0.process(data)
332            }
333
334            /// Update the hashing state by adding the input bytes slice into the state
335            pub fn update(mut self, data: &[u8]) -> Self {
336                self.0.process(data);
337                self
338            }
339
340            /// Same as `finalize` but do not consume the context, but instead
341            /// reset it in a ready to use state.
342            pub fn finalize_reset(&mut self) -> [u8; $digestlength] {
343                let mut out = [0; $digestlength];
344                self.0.output(&mut out);
345                self.0.reset();
346                out
347            }
348
349            /// Finalize the context and return an array of bytes
350            ///
351            /// The context is consumed by this function, to prevent buggy reuse.
352            ///
353            /// If the context need to be kept before finalizing, the user can clone the Context
354            pub fn finalize(mut self) -> [u8; $digestlength] {
355                let mut out = [0; $digestlength];
356                self.0.output(&mut out);
357                out
358            }
359
360            /// Reset the context state, as if a new context had been created
361            pub fn reset(&mut self) {
362                self.0.reset()
363            }
364        }
365    };
366}
367
368sha3_impl!(Sha3_224, Context224, 28, "SHA3 224");
369sha3_impl!(Sha3_256, Context256, 32, "SHA3 256");
370sha3_impl!(Sha3_384, Context384, 48, "SHA3 384");
371sha3_impl!(Sha3_512, Context512, 64, "SHA3 512");
372
373#[cfg(test)]
374mod tests {
375    use super::super::tests::{test_hashing, Test};
376    use super::*;
377
378    #[test]
379    fn test_sha3_224() {
380        let tests = [
381            Test {
382                input: b"",
383                output: [
384                    0x6b, 0x4e, 0x03, 0x42, 0x36, 0x67, 0xdb, 0xb7, 0x3b, 0x6e, 0x15, 0x45, 0x4f,
385                    0x0e, 0xb1, 0xab, 0xd4, 0x59, 0x7f, 0x9a, 0x1b, 0x07, 0x8e, 0x3f, 0x5b, 0x5a,
386                    0x6b, 0xc7,
387                ],
388            },
389            Test {
390                input: b"The quick brown fox jumps over the lazy dog",
391                output: [
392                    0xd1, 0x5d, 0xad, 0xce, 0xaa, 0x4d, 0x5d, 0x7b, 0xb3, 0xb4, 0x8f, 0x44, 0x64,
393                    0x21, 0xd5, 0x42, 0xe0, 0x8a, 0xd8, 0x88, 0x73, 0x05, 0xe2, 0x8d, 0x58, 0x33,
394                    0x57, 0x95,
395                ],
396            },
397            Test {
398                input: b"The quick brown fox jumps over the lazy dog.",
399                output: [
400                    0x2d, 0x07, 0x08, 0x90, 0x38, 0x33, 0xaf, 0xab, 0xdd, 0x23, 0x2a, 0x20, 0x20,
401                    0x11, 0x76, 0xe8, 0xb5, 0x8c, 0x5b, 0xe8, 0xa6, 0xfe, 0x74, 0x26, 0x5a, 0xc5,
402                    0x4d, 0xb0,
403                ],
404            },
405        ];
406        test_hashing(
407            &tests,
408            Sha3_224,
409            |_| Context224::new(),
410            |ctx, input| ctx.update(input),
411            |ctx, input| ctx.update_mut(input),
412            |ctx| ctx.finalize(),
413            |ctx| ctx.finalize_reset(),
414            |ctx| ctx.reset(),
415        )
416    }
417
418    #[test]
419    fn test_sha3_256() {
420        let tests = [
421            Test {
422                input: b"",
423                output: [
424                    0xa7, 0xff, 0xc6, 0xf8, 0xbf, 0x1e, 0xd7, 0x66, 0x51, 0xc1, 0x47, 0x56, 0xa0,
425                    0x61, 0xd6, 0x62, 0xf5, 0x80, 0xff, 0x4d, 0xe4, 0x3b, 0x49, 0xfa, 0x82, 0xd8,
426                    0x0a, 0x4b, 0x80, 0xf8, 0x43, 0x4a,
427                ],
428            },
429            Test {
430                input: b"The quick brown fox jumps over the lazy dog",
431                output: [
432                    0x69, 0x07, 0x0d, 0xda, 0x01, 0x97, 0x5c, 0x8c, 0x12, 0x0c, 0x3a, 0xad, 0xa1,
433                    0xb2, 0x82, 0x39, 0x4e, 0x7f, 0x03, 0x2f, 0xa9, 0xcf, 0x32, 0xf4, 0xcb, 0x22,
434                    0x59, 0xa0, 0x89, 0x7d, 0xfc, 0x04,
435                ],
436            },
437            Test {
438                input: b"The quick brown fox jumps over the lazy dog.",
439                output: [
440                    0xa8, 0x0f, 0x83, 0x9c, 0xd4, 0xf8, 0x3f, 0x6c, 0x3d, 0xaf, 0xc8, 0x7f, 0xea,
441                    0xe4, 0x70, 0x04, 0x5e, 0x4e, 0xb0, 0xd3, 0x66, 0x39, 0x7d, 0x5c, 0x6c, 0xe3,
442                    0x4b, 0xa1, 0x73, 0x9f, 0x73, 0x4d,
443                ],
444            },
445        ];
446        test_hashing(
447            &tests,
448            Sha3_256,
449            |_| Context256::new(),
450            |ctx, input| ctx.update(input),
451            |ctx, input| ctx.update_mut(input),
452            |ctx| ctx.finalize(),
453            |ctx| ctx.finalize_reset(),
454            |ctx| ctx.reset(),
455        )
456    }
457
458    #[test]
459    fn test_sha3_384() {
460        let tests = [
461            Test {
462                input: b"",
463                output: [
464                    0x0c, 0x63, 0xa7, 0x5b, 0x84, 0x5e, 0x4f, 0x7d, 0x01, 0x10, 0x7d, 0x85, 0x2e,
465                    0x4c, 0x24, 0x85, 0xc5, 0x1a, 0x50, 0xaa, 0xaa, 0x94, 0xfc, 0x61, 0x99, 0x5e,
466                    0x71, 0xbb, 0xee, 0x98, 0x3a, 0x2a, 0xc3, 0x71, 0x38, 0x31, 0x26, 0x4a, 0xdb,
467                    0x47, 0xfb, 0x6b, 0xd1, 0xe0, 0x58, 0xd5, 0xf0, 0x04,
468                ],
469            },
470            Test {
471                input: b"The quick brown fox jumps over the lazy dog",
472                output: [
473                    0x70, 0x63, 0x46, 0x5e, 0x08, 0xa9, 0x3b, 0xce, 0x31, 0xcd, 0x89, 0xd2, 0xe3,
474                    0xca, 0x8f, 0x60, 0x24, 0x98, 0x69, 0x6e, 0x25, 0x35, 0x92, 0xed, 0x26, 0xf0,
475                    0x7b, 0xf7, 0xe7, 0x03, 0xcf, 0x32, 0x85, 0x81, 0xe1, 0x47, 0x1a, 0x7b, 0xa7,
476                    0xab, 0x11, 0x9b, 0x1a, 0x9e, 0xbd, 0xf8, 0xbe, 0x41,
477                ],
478            },
479            Test {
480                input: b"The quick brown fox jumps over the lazy dog.",
481                output: [
482                    0x1a, 0x34, 0xd8, 0x16, 0x95, 0xb6, 0x22, 0xdf, 0x17, 0x8b, 0xc7, 0x4d, 0xf7,
483                    0x12, 0x4f, 0xe1, 0x2f, 0xac, 0x0f, 0x64, 0xba, 0x52, 0x50, 0xb7, 0x8b, 0x99,
484                    0xc1, 0x27, 0x3d, 0x4b, 0x08, 0x01, 0x68, 0xe1, 0x06, 0x52, 0x89, 0x4e, 0xca,
485                    0xd5, 0xf1, 0xf4, 0xd5, 0xb9, 0x65, 0x43, 0x7f, 0xb9,
486                ],
487            },
488        ];
489        test_hashing(
490            &tests,
491            Sha3_384,
492            |_| Context384::new(),
493            |ctx, input| ctx.update(input),
494            |ctx, input| ctx.update_mut(input),
495            |ctx| ctx.finalize(),
496            |ctx| ctx.finalize_reset(),
497            |ctx| ctx.reset(),
498        )
499    }
500
501    #[test]
502    fn test_sha3_512() {
503        let tests = [
504            Test {
505                input: b"",
506                output: [
507                    0xa6, 0x9f, 0x73, 0xcc, 0xa2, 0x3a, 0x9a, 0xc5, 0xc8, 0xb5, 0x67, 0xdc, 0x18,
508                    0x5a, 0x75, 0x6e, 0x97, 0xc9, 0x82, 0x16, 0x4f, 0xe2, 0x58, 0x59, 0xe0, 0xd1,
509                    0xdc, 0xc1, 0x47, 0x5c, 0x80, 0xa6, 0x15, 0xb2, 0x12, 0x3a, 0xf1, 0xf5, 0xf9,
510                    0x4c, 0x11, 0xe3, 0xe9, 0x40, 0x2c, 0x3a, 0xc5, 0x58, 0xf5, 0x00, 0x19, 0x9d,
511                    0x95, 0xb6, 0xd3, 0xe3, 0x01, 0x75, 0x85, 0x86, 0x28, 0x1d, 0xcd, 0x26,
512                ],
513            },
514            Test {
515                input: b"The quick brown fox jumps over the lazy dog",
516                output: [
517                    0x01, 0xde, 0xdd, 0x5d, 0xe4, 0xef, 0x14, 0x64, 0x24, 0x45, 0xba, 0x5f, 0x5b,
518                    0x97, 0xc1, 0x5e, 0x47, 0xb9, 0xad, 0x93, 0x13, 0x26, 0xe4, 0xb0, 0x72, 0x7c,
519                    0xd9, 0x4c, 0xef, 0xc4, 0x4f, 0xff, 0x23, 0xf0, 0x7b, 0xf5, 0x43, 0x13, 0x99,
520                    0x39, 0xb4, 0x91, 0x28, 0xca, 0xf4, 0x36, 0xdc, 0x1b, 0xde, 0xe5, 0x4f, 0xcb,
521                    0x24, 0x02, 0x3a, 0x08, 0xd9, 0x40, 0x3f, 0x9b, 0x4b, 0xf0, 0xd4, 0x50,
522                ],
523            },
524            Test {
525                input: b"The quick brown fox jumps over the lazy dog.",
526                output: [
527                    0x18, 0xf4, 0xf4, 0xbd, 0x41, 0x96, 0x03, 0xf9, 0x55, 0x38, 0x83, 0x70, 0x03,
528                    0xd9, 0xd2, 0x54, 0xc2, 0x6c, 0x23, 0x76, 0x55, 0x65, 0x16, 0x22, 0x47, 0x48,
529                    0x3f, 0x65, 0xc5, 0x03, 0x03, 0x59, 0x7b, 0xc9, 0xce, 0x4d, 0x28, 0x9f, 0x21,
530                    0xd1, 0xc2, 0xf1, 0xf4, 0x58, 0x82, 0x8e, 0x33, 0xdc, 0x44, 0x21, 0x00, 0x33,
531                    0x1b, 0x35, 0xe7, 0xeb, 0x03, 0x1b, 0x5d, 0x38, 0xba, 0x64, 0x60, 0xf8,
532                ],
533            },
534        ];
535        test_hashing(
536            &tests,
537            Sha3_512,
538            |_| Context512::new(),
539            |ctx, input| ctx.update(input),
540            |ctx, input| ctx.update_mut(input),
541            |ctx| ctx.finalize(),
542            |ctx| ctx.finalize_reset(),
543            |ctx| ctx.reset(),
544        )
545    }
546}