Skip to main content

orion/hazardous/mac/
blake2b.rs

1// MIT License
2
3// Copyright (c) 2018-2026 The orion Developers
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11
12// The above copyright notice and this permission notice shall be included in
13// all copies or substantial portions of the Software.
14
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23//! # Parameters:
24//! - `secret_key`: The authentication key.
25//! - `size`: The desired output length for the authentication tag.
26//! - `data`: Data to be authenticated.
27//! - `expected`: The expected authentication tag.
28//!
29//! # Errors:
30//! An error will be returned if:
31//! - `size` is 0 or greater than 64.
32//! - [`finalize()`] is called twice without a [`reset()`] in between.
33//! - [`update()`] is called after [`finalize()`] without a [`reset()`] in
34//!   between.
35//!
36//! # Panics:
37//! A panic will occur if:
38//! - More than 2*(2^64-1) bytes of data are hashed.
39//!
40//! # Security:
41//! - The secret key should always be generated using a CSPRNG.
42//!   [`SecretKey::generate()`] can be used for this. It generates
43//!   a secret key of 32 bytes.
44//! - The minimum recommended size for a secret key is 32 bytes.
45//! - The recommended minimum output size is 32.
46//! - This interface only allows creating authentication tag using BLAKE2b. If hash digests are needed,
47//! please refer to the [`hash::blake2::blake2b`] module.
48//!
49//! # Example:
50//! ```rust
51//! # #[cfg(feature = "safe_api")] {
52//! use orion::hazardous::mac::blake2b::{Blake2b, SecretKey};
53//!
54//! let key = SecretKey::generate();
55//!
56//! let mut state = Blake2b::new(&key, 64)?;
57//! state.update(b"Some data")?;
58//! let tag = state.finalize()?;
59//!
60//! assert!(Blake2b::verify(&tag, &key, 64, b"Some data").is_ok());
61//! # }
62//! # Ok::<(), orion::errors::UnknownCryptoError>(())
63//! ```
64//! [`update()`]: blake2b::Blake2b::update
65//! [`reset()`]: blake2b::Blake2b::reset
66//! [`finalize()`]: blake2b::Blake2b::finalize
67//! [`SecretKey::generate()`]: blake2b::SecretKey::generate
68//! [`hash::blake2::blake2b`]: crate::hazardous::hash::blake2::blake2b
69
70use crate::errors::UnknownCryptoError;
71use crate::hazardous::hash::blake2::blake2b_core::{self, BLAKE2B_KEYSIZE, BLAKE2B_OUTSIZE};
72
73construct_secret_key! {
74    /// A type to represent the secret key that BLAKE2b uses for keyed mode.
75    ///
76    /// # Errors:
77    /// An error will be returned if:
78    /// - `slice` is empty.
79    /// - `slice` is greater than 64 bytes.
80    ///
81    /// # Panics:
82    /// A panic will occur if:
83    /// - Failure to generate random bytes securely.
84    (SecretKey, test_secret_key, 1, BLAKE2B_KEYSIZE, 32)
85}
86
87construct_tag! {
88    /// A type to represent the `Tag` that BLAKE2b returns.
89    ///
90    /// # Errors:
91    /// An error will be returned if:
92    /// - `slice` is empty.
93    /// - `slice` is greater than 64 bytes.
94    (Tag, test_tag, 1, BLAKE2B_OUTSIZE)
95}
96
97#[derive(Debug, Clone)]
98/// BLAKE2b streaming state.
99pub struct Blake2b {
100    _state: blake2b_core::State,
101}
102
103impl Blake2b {
104    #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
105    /// Initialize a `Blake2b` struct with a given size (in bytes) and key.
106    pub fn new(secret_key: &SecretKey, size: usize) -> Result<Self, UnknownCryptoError> {
107        Ok(Self {
108            _state: blake2b_core::State::_new(secret_key.unprotected_as_bytes(), size)?,
109        })
110    }
111
112    #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
113    /// Reset to `new()` state.
114    pub fn reset(&mut self, secret_key: &SecretKey) -> Result<(), UnknownCryptoError> {
115        self._state._reset(secret_key.unprotected_as_bytes())
116    }
117
118    #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
119    /// Update state with `data`. This can be called multiple times.
120    pub fn update(&mut self, data: &[u8]) -> Result<(), UnknownCryptoError> {
121        self._state._update(data)
122    }
123
124    #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
125    /// Return a BLAKE2b tag.
126    pub fn finalize(&mut self) -> Result<Tag, UnknownCryptoError> {
127        let mut tmp = zeroize_wrap!([0u8; BLAKE2B_OUTSIZE]);
128        self._state._finalize(&mut tmp)?;
129
130        Tag::from_slice(&tmp[..self._state.size])
131    }
132
133    #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
134    /// Verify a BLAKE2b tag in constant time.
135    pub fn verify(
136        expected: &Tag,
137        secret_key: &SecretKey,
138        size: usize,
139        data: &[u8],
140    ) -> Result<(), UnknownCryptoError> {
141        let mut ctx = Self::new(secret_key, size)?;
142        ctx.update(data)?;
143
144        if &ctx.finalize()? == expected {
145            Ok(())
146        } else {
147            Err(UnknownCryptoError)
148        }
149    }
150}
151
152#[cfg(test)]
153mod public {
154    mod test_streaming_interface_no_key {
155        use crate::errors::UnknownCryptoError;
156        use crate::hazardous::hash::blake2::blake2b_core::{
157            compare_blake2b_states, BLAKE2B_BLOCKSIZE, BLAKE2B_OUTSIZE,
158        };
159        use crate::hazardous::mac::blake2b::{Blake2b, SecretKey, Tag};
160        use crate::test_framework::incremental_interface::{
161            StreamingContextConsistencyTester, TestableStreamingContext,
162        };
163
164        const KEY: [u8; 32] = [255u8; 32];
165
166        impl TestableStreamingContext<Tag> for Blake2b {
167            fn reset(&mut self) -> Result<(), UnknownCryptoError> {
168                let key = SecretKey::from_slice(&KEY).unwrap();
169                self.reset(&key)
170            }
171
172            fn update(&mut self, input: &[u8]) -> Result<(), UnknownCryptoError> {
173                self.update(input)
174            }
175
176            fn finalize(&mut self) -> Result<Tag, UnknownCryptoError> {
177                self.finalize()
178            }
179
180            fn one_shot(input: &[u8]) -> Result<Tag, UnknownCryptoError> {
181                let key = SecretKey::from_slice(&KEY).unwrap();
182                let mut ctx = Blake2b::new(&key, BLAKE2B_OUTSIZE)?;
183                ctx.update(input)?;
184                ctx.finalize()
185            }
186
187            fn verify_result(expected: &Tag, input: &[u8]) -> Result<(), UnknownCryptoError> {
188                let actual = Self::one_shot(input)?;
189
190                if &actual == expected {
191                    Ok(())
192                } else {
193                    Err(UnknownCryptoError)
194                }
195            }
196
197            fn compare_states(state_1: &Blake2b, state_2: &Blake2b) {
198                compare_blake2b_states(&state_1._state, &state_2._state)
199            }
200        }
201
202        #[test]
203        fn default_consistency_tests() {
204            let key = SecretKey::from_slice(&KEY).unwrap();
205            let initial_state: Blake2b = Blake2b::new(&key, BLAKE2B_OUTSIZE).unwrap();
206
207            let test_runner = StreamingContextConsistencyTester::<Tag, Blake2b>::new(
208                initial_state,
209                BLAKE2B_BLOCKSIZE,
210            );
211            test_runner.run_all_tests();
212        }
213
214        #[quickcheck]
215        #[cfg(feature = "safe_api")]
216        /// Related bug: https://github.com/orion-rs/orion/issues/46
217        /// Test different streaming state usage patterns.
218        fn prop_input_to_consistency(data: Vec<u8>) -> bool {
219            let key = SecretKey::from_slice(&KEY).unwrap();
220            let initial_state: Blake2b = Blake2b::new(&key, BLAKE2B_OUTSIZE).unwrap();
221
222            let test_runner = StreamingContextConsistencyTester::<Tag, Blake2b>::new(
223                initial_state,
224                BLAKE2B_BLOCKSIZE,
225            );
226            test_runner.run_all_tests_property(&data);
227            true
228        }
229    }
230
231    mod test_new {
232        use crate::hazardous::mac::blake2b::{Blake2b, SecretKey};
233
234        #[test]
235        fn test_init_size() {
236            let sk = SecretKey::from_slice(&[0u8; 32]).unwrap();
237            assert!(Blake2b::new(&sk, 0).is_err());
238            assert!(Blake2b::new(&sk, 65).is_err());
239            assert!(Blake2b::new(&sk, 1).is_ok());
240            assert!(Blake2b::new(&sk, 64).is_ok());
241        }
242    }
243
244    #[cfg(feature = "safe_api")]
245    mod test_verify {
246        use crate::hazardous::mac::blake2b::{Blake2b, SecretKey};
247
248        #[quickcheck]
249        #[cfg(feature = "safe_api")]
250        /// When using a different key, verify() should always yield an error.
251        /// NOTE: Using different and same input data is tested with TestableStreamingContext.
252        fn prop_verify_diff_key_false(data: Vec<u8>) -> bool {
253            let sk = SecretKey::generate();
254            let mut state = Blake2b::new(&sk, 64).unwrap();
255            state.update(&data[..]).unwrap();
256            let tag = state.finalize().unwrap();
257            let bad_sk = SecretKey::generate();
258
259            Blake2b::verify(&tag, &bad_sk, 64, &data[..]).is_err()
260        }
261
262        #[quickcheck]
263        #[cfg(feature = "safe_api")]
264        /// When using a different size, verify() should always yield an error.
265        /// NOTE: Using different and same input data is tested with TestableStreamingContext.
266        fn prop_verify_diff_size_false(data: Vec<u8>, size_one: usize, size_two: usize) -> bool {
267            let (size_one, size_two) = match (size_one, size_two) {
268                (1..=64, 1..=64) => (size_one, size_two),
269                (_, _) => (32, 64),
270            };
271
272            let sk = SecretKey::generate();
273            let mut state = Blake2b::new(&sk, size_one).unwrap();
274            state.update(&data[..]).unwrap();
275            let tag = state.finalize().unwrap();
276
277            if size_one != size_two {
278                Blake2b::verify(&tag, &sk, size_two, &data[..]).is_err()
279            } else {
280                Blake2b::verify(&tag, &sk, size_two, &data[..]).is_ok()
281            }
282        }
283    }
284
285    mod test_streaming_interface {
286        use crate::hazardous::hash::blake2::blake2b_core::compare_blake2b_states;
287        use crate::hazardous::mac::blake2b::{Blake2b, SecretKey};
288
289        /// Related bug: https://github.com/orion-rs/orion/issues/46
290        /// Testing different usage combinations of new(), update(),
291        /// finalize() and reset() produce the same Digest/Tag.
292        fn produces_same_hash(sk: &SecretKey, size: usize, data: &[u8]) {
293            // new(), update(), finalize()
294            let mut state_1 = Blake2b::new(sk, size).unwrap();
295            state_1.update(data).unwrap();
296            let res_1 = state_1.finalize().unwrap();
297
298            // new(), reset(), update(), finalize()
299            let mut state_2 = Blake2b::new(sk, size).unwrap();
300            state_2.reset(sk).unwrap();
301            state_2.update(data).unwrap();
302            let res_2 = state_2.finalize().unwrap();
303
304            // new(), update(), reset(), update(), finalize()
305            let mut state_3 = Blake2b::new(sk, size).unwrap();
306            state_3.update(data).unwrap();
307            state_3.reset(sk).unwrap();
308            state_3.update(data).unwrap();
309            let res_3 = state_3.finalize().unwrap();
310
311            // new(), update(), finalize(), reset(), update(), finalize()
312            let mut state_4 = Blake2b::new(sk, size).unwrap();
313            state_4.update(data).unwrap();
314            let _ = state_4.finalize().unwrap();
315            state_4.reset(sk).unwrap();
316            state_4.update(data).unwrap();
317            let res_4 = state_4.finalize().unwrap();
318
319            assert_eq!(res_1, res_2);
320            assert_eq!(res_2, res_3);
321            assert_eq!(res_3, res_4);
322
323            // Tests for the assumption that returning Ok() on empty update() calls
324            // with streaming APIs, gives the correct result. This is done by testing
325            // the reasoning that if update() is empty, returns Ok(), it is the same as
326            // calling new() -> finalize(). i.e not calling update() at all.
327            if data.is_empty() {
328                // new(), finalize()
329                let mut state_5 = Blake2b::new(sk, size).unwrap();
330                let res_5 = state_5.finalize().unwrap();
331
332                // new(), reset(), finalize()
333                let mut state_6 = Blake2b::new(sk, size).unwrap();
334                state_6.reset(sk).unwrap();
335                let res_6 = state_6.finalize().unwrap();
336
337                // new(), update(), reset(), finalize()
338                let mut state_7 = Blake2b::new(sk, size).unwrap();
339                state_7.update(b"Wrong data").unwrap();
340                state_7.reset(sk).unwrap();
341                let res_7 = state_7.finalize().unwrap();
342
343                assert_eq!(res_4, res_5);
344                assert_eq!(res_5, res_6);
345                assert_eq!(res_6, res_7);
346            }
347        }
348
349        /// Related bug: https://github.com/orion-rs/orion/issues/46
350        /// Testing different usage combinations of new(), update(),
351        /// finalize() and reset() produce the same Digest/Tag.
352        fn produces_same_state(sk: &SecretKey, size: usize, data: &[u8]) {
353            // new()
354            let state_1 = Blake2b::new(sk, size).unwrap();
355
356            // new(), reset()
357            let mut state_2 = Blake2b::new(sk, size).unwrap();
358            state_2.reset(sk).unwrap();
359
360            // new(), update(), reset()
361            let mut state_3 = Blake2b::new(sk, size).unwrap();
362            state_3.update(data).unwrap();
363            state_3.reset(sk).unwrap();
364
365            // new(), update(), finalize(), reset()
366            let mut state_4 = Blake2b::new(sk, size).unwrap();
367            state_4.update(data).unwrap();
368            let _ = state_4.finalize().unwrap();
369            state_4.reset(sk).unwrap();
370
371            compare_blake2b_states(&state_1._state, &state_2._state);
372            compare_blake2b_states(&state_2._state, &state_3._state);
373            compare_blake2b_states(&state_3._state, &state_4._state);
374        }
375
376        #[test]
377        /// Related bug: https://github.com/orion-rs/orion/issues/46
378        fn test_produce_same_state() {
379            let sk = SecretKey::from_slice(b"Testing").unwrap();
380            produces_same_state(&sk, 1, b"Tests");
381            produces_same_state(&sk, 32, b"Tests");
382            produces_same_state(&sk, 64, b"Tests");
383            produces_same_state(&sk, 28, b"Tests");
384        }
385
386        #[test]
387        /// Related bug: https://github.com/orion-rs/orion/issues/46
388        fn test_produce_same_hash() {
389            let sk = SecretKey::from_slice(b"Testing").unwrap();
390            produces_same_hash(&sk, 1, b"Tests");
391            produces_same_hash(&sk, 32, b"Tests");
392            produces_same_hash(&sk, 64, b"Tests");
393            produces_same_hash(&sk, 28, b"Tests");
394
395            produces_same_hash(&sk, 1, b"");
396            produces_same_hash(&sk, 32, b"");
397            produces_same_hash(&sk, 64, b"");
398            produces_same_hash(&sk, 28, b"");
399        }
400
401        #[quickcheck]
402        #[cfg(feature = "safe_api")]
403        /// Related bug: https://github.com/orion-rs/orion/issues/46
404        /// Test different streaming state usage patterns.
405        fn prop_same_hash_different_usage(data: Vec<u8>, size: usize) -> bool {
406            use crate::hazardous::hash::blake2::blake2b_core::BLAKE2B_OUTSIZE;
407
408            if (1..=BLAKE2B_OUTSIZE).contains(&size) {
409                // Will panic on incorrect results.
410                let sk = SecretKey::generate();
411                produces_same_hash(&sk, size, &data[..]);
412            }
413
414            true
415        }
416
417        #[quickcheck]
418        #[cfg(feature = "safe_api")]
419        /// Related bug: https://github.com/orion-rs/orion/issues/46
420        /// Test different streaming state usage patterns.
421        fn prop_same_state_different_usage(data: Vec<u8>, size: usize) -> bool {
422            use crate::hazardous::hash::blake2::blake2b_core::BLAKE2B_OUTSIZE;
423
424            if (1..=BLAKE2B_OUTSIZE).contains(&size) {
425                // Will panic on incorrect results.
426                let sk = SecretKey::generate();
427                produces_same_state(&sk, size, &data[..]);
428            }
429
430            true
431        }
432    }
433}