dryoc/classic/crypto_generichash.rs
1//! # Generic hashing
2//!
3//! Implements libsodium's generic hashing functions with BLAKE2b. With a secret
4//! key, BLAKE2b acts as a message authentication code (MAC) or pseudorandom
5//! function (PRF); it is not HMAC.
6//!
7//! See the [libsodium documentation](https://doc.libsodium.org/hashing/generic_hashing)
8//! for details.
9//!
10//! # Classic API example, single-part interface
11//!
12//! ```
13//! use base64::Engine as _;
14//! use base64::engine::general_purpose;
15//! use dryoc::classic::crypto_generichash::*;
16//! use dryoc::constants::CRYPTO_GENERICHASH_BYTES;
17//!
18//! // Use the default hash length
19//! let mut output = [0u8; CRYPTO_GENERICHASH_BYTES];
20//! // Compute the hash using the single-part interface
21//! crypto_generichash(&mut output, b"a string of bytes", None).ok();
22//!
23//! assert_eq!(
24//! general_purpose::STANDARD.encode(output),
25//! "GdztjR9nU/rLh8VJt8e74+/seKTUnHgBexhGSpxLau0="
26//! );
27//! ```
28//!
29//! # Classic API example, incremental interface
30//!
31//! ```
32//! use base64::Engine as _;
33//! use base64::engine::general_purpose;
34//! use dryoc::classic::crypto_generichash::*;
35//! use dryoc::constants::CRYPTO_GENERICHASH_BYTES;
36//!
37//! // Use the default hash length
38//! let mut output = [0u8; CRYPTO_GENERICHASH_BYTES];
39//! // Initialize the state for the incremental interface
40//! let mut state = crypto_generichash_init(None, CRYPTO_GENERICHASH_BYTES).expect("state");
41//! // Update the hash
42//! crypto_generichash_update(&mut state, b"a string of bytes");
43//! // Finalize, compute the hash and copy it into `output`
44//! crypto_generichash_final(state, &mut output).expect("final failed");
45//!
46//! assert_eq!(
47//! general_purpose::STANDARD.encode(output),
48//! "GdztjR9nU/rLh8VJt8e74+/seKTUnHgBexhGSpxLau0="
49//! );
50//! ```
51use super::generichash_blake2b::*;
52use crate::blake2b;
53use crate::constants::CRYPTO_GENERICHASH_KEYBYTES;
54use crate::error::Error;
55
56/**
57Computes a hash from `input` and `key`, copying the result into `output`.
58
59| Parameter | Typical length | Recommended minimum | Accepted lengths |
60|-|-|-|-|
61| `output` | [`CRYPTO_GENERICHASH_BYTES`](crate::constants::CRYPTO_GENERICHASH_BYTES) | [`CRYPTO_GENERICHASH_BYTES_MIN`](crate::constants::CRYPTO_GENERICHASH_BYTES_MIN) | 1 to [`CRYPTO_GENERICHASH_BYTES_MAX`](crate::constants::CRYPTO_GENERICHASH_BYTES_MAX) |
62| `key` | [`CRYPTO_GENERICHASH_KEYBYTES`] | [`CRYPTO_GENERICHASH_KEYBYTES_MIN`](crate::constants::CRYPTO_GENERICHASH_KEYBYTES_MIN) | 0 to [`CRYPTO_GENERICHASH_KEYBYTES_MAX`](crate::constants::CRYPTO_GENERICHASH_KEYBYTES_MAX) |
63
64As in libsodium, the `*_MIN` constants are recommendations rather than
65limits, and an empty key (`Some(&[])`) computes the same unkeyed hash as
66`None`.
67
68Compatible with libsodium's `crypto_generichash`.
69
70# Errors
71
72Returns an error if the output or key length is outside the accepted range.
73*/
74#[inline]
75pub fn crypto_generichash(
76 output: &mut [u8],
77 input: &[u8],
78 key: Option<&[u8]>,
79) -> Result<(), Error> {
80 crypto_generichash_blake2b(output, input, key)
81}
82
83/// State struct for the generic hash algorithm, based on BLAKE2B.
84///
85/// Cloning copies the in-progress state, so both copies can be finished
86/// independently; each copy is wiped when dropped.
87#[derive(Clone)]
88pub struct GenericHashState {
89 state: blake2b::State,
90}
91
92/**
93Initializes the state for the generic hash function using `outlen` for the expected hash output length, and optional `key`, returning it upon success.
94
95| Parameter | Typical length | Recommended minimum | Accepted lengths |
96|-|-|-|-|
97| `outlen` | [`CRYPTO_GENERICHASH_BYTES`](crate::constants::CRYPTO_GENERICHASH_BYTES) | [`CRYPTO_GENERICHASH_BYTES_MIN`](crate::constants::CRYPTO_GENERICHASH_BYTES_MIN) | 1 to [`CRYPTO_GENERICHASH_BYTES_MAX`](crate::constants::CRYPTO_GENERICHASH_BYTES_MAX) |
98| `key` | [`CRYPTO_GENERICHASH_KEYBYTES`] | [`CRYPTO_GENERICHASH_KEYBYTES_MIN`](crate::constants::CRYPTO_GENERICHASH_KEYBYTES_MIN) | 0 to [`CRYPTO_GENERICHASH_KEYBYTES_MAX`](crate::constants::CRYPTO_GENERICHASH_KEYBYTES_MAX) |
99
100As in libsodium, the `*_MIN` constants are recommendations rather than
101limits, and an empty key (`Some(&[])`) is the same as `None`.
102
103Equivalent to libsodium's `crypto_generichash_init`.
104
105# Errors
106
107Returns an error if `outlen` or the key length is outside the accepted range.
108*/
109#[inline]
110pub fn crypto_generichash_init(
111 key: Option<&[u8]>,
112 outlen: usize,
113) -> Result<GenericHashState, Error> {
114 let state = crypto_generichash_blake2b_init(key, outlen, None, None)?;
115 Ok(GenericHashState { state })
116}
117
118/// Updates the internal hash state with `input`.
119///
120/// Equivalent to libsodium's `crypto_generichash_update`
121#[inline]
122pub fn crypto_generichash_update(state: &mut GenericHashState, input: &[u8]) {
123 crypto_generichash_blake2b_update(&mut state.state, input)
124}
125
126/// Finalizes the hash computation, copying the result into `output`, whose
127/// length should equal `outlen` from the call to [`crypto_generichash_init`].
128///
129/// As in libsodium, a different length is not rejected: `output` receives its
130/// length's worth (1 to 64 bytes) of the BLAKE2b chaining value computed for
131/// the `init` length, so a shorter `output` truncates the digest and a longer
132/// one appends chaining-value bytes that are not part of it.
133///
134/// Equivalent to libsodium's `crypto_generichash_final`
135///
136/// # Errors
137///
138/// Returns an error if `output` is empty or longer than 64 bytes, lengths
139/// for which libsodium aborts.
140#[inline]
141pub fn crypto_generichash_final(state: GenericHashState, output: &mut [u8]) -> Result<(), Error> {
142 crypto_generichash_blake2b_final(state.state, output)
143}
144
145/// Generates a random hash key using the OS's random number source.
146///
147/// Equivalent to libsodium's `crypto_generichash_keygen`
148#[must_use]
149pub fn crypto_generichash_keygen() -> [u8; CRYPTO_GENERICHASH_KEYBYTES] {
150 let mut key = [0u8; CRYPTO_GENERICHASH_KEYBYTES];
151 crate::rng::copy_randombytes(&mut key);
152 key
153}
154
155#[cfg(all(test, dryoc_native_tests))]
156mod tests {
157 use super::*;
158 use crate::constants::CRYPTO_GENERICHASH_KEYBYTES_MAX;
159 use crate::native_test_util;
160 use crate::test_prelude::*;
161
162 /// Output lengths around libsodium's accepted range (1 to 64) and the
163 /// recommended minimum (16).
164 const OUTLENS: [usize; 6] = [0, 1, 15, 16, 64, 65];
165 /// Key lengths around libsodium's accepted range (0 to 64) and the
166 /// recommended minimum (16).
167 const KEYLENS: [usize; 6] = [0, 1, 15, 16, 64, 65];
168
169 fn message(len: usize) -> Vec<u8> {
170 (0..len as u32).map(|i| (i * 31 % 251) as u8).collect()
171 }
172
173 /// `None` and keys of every length in [`KEYLENS`].
174 fn keys(key: &[u8]) -> impl Iterator<Item = Option<&[u8]>> {
175 core::iter::once(None).chain(KEYLENS.into_iter().map(|len| Some(&key[..len])))
176 }
177
178 /// `input` cut at the BLAKE2b block boundaries, with empty updates
179 /// between the pieces.
180 fn parts(input: &[u8]) -> Vec<&[u8]> {
181 let mut parts = Vec::new();
182 let mut start = 0;
183 for cut in [0usize, 1, 127, 128, 129, input.len()] {
184 let cut = cut.clamp(start, input.len());
185 parts.push(&input[start..cut]);
186 parts.push(&[][..]);
187 start = cut;
188 }
189 parts
190 }
191
192 fn ours_incremental(
193 key: Option<&[u8]>,
194 init_outlen: usize,
195 parts: &[&[u8]],
196 final_outlen: usize,
197 ) -> Result<Vec<u8>, Error> {
198 let mut state = crypto_generichash_init(key, init_outlen)?;
199 for part in parts {
200 crypto_generichash_update(&mut state, part);
201 }
202 let mut output = vec![0u8; final_outlen];
203 crypto_generichash_final(state, &mut output)?;
204 Ok(output)
205 }
206
207 /// One-shot and incremental hashing accept exactly the output and key
208 /// lengths libsodium accepts (below the recommended minimums included),
209 /// treat an empty key as no key, and produce libsodium's digests,
210 /// including across BLAKE2b block boundaries.
211 #[test]
212 fn output_and_key_lengths_match_libsodium() {
213 let key: Vec<u8> = (0..=CRYPTO_GENERICHASH_KEYBYTES_MAX as u8)
214 .map(|i| i.wrapping_mul(37).wrapping_add(11))
215 .collect();
216 for len in [0usize, 1, 127, 128, 129, 300] {
217 let input = message(len);
218 let parts = parts(&input);
219 for outlen in OUTLENS {
220 for key in keys(&key) {
221 let context =
222 format!("len {len}, outlen {outlen}, key {:?}", key.map(<[u8]>::len));
223 let expected = native_test_util::generichash(outlen, &input, key);
224
225 let mut one_shot = vec![0u8; outlen];
226 let actual = crypto_generichash(&mut one_shot, &input, key).map(|()| one_shot);
227 assert_eq!(actual.map_err(drop), expected, "one-shot {context}");
228
229 // libsodium aborts on an out-of-range final length, so the
230 // incremental oracle finalizes with a valid one; `init`
231 // rejects the same lengths as the one-shot call.
232 let expected = native_test_util::generichash_multipart(
233 key,
234 outlen,
235 &parts,
236 outlen.clamp(1, 64),
237 );
238 let actual = ours_incremental(key, outlen, &parts, outlen);
239 assert_eq!(actual.map_err(drop), expected, "incremental {context}");
240 }
241 }
242 }
243 }
244
245 /// Like libsodium, `crypto_generichash_final` does not check the output
246 /// length against the one given to `crypto_generichash_init`: it writes
247 /// the first `output.len()` bytes (1 to 64) of the BLAKE2b chaining value,
248 /// which is parameterized by the `init` length. dryoc rejects final
249 /// lengths outside 1 to 64 with an error, where libsodium aborts.
250 #[test]
251 fn final_with_mismatched_output_length_matches_libsodium() {
252 let input = message(200);
253 let parts = parts(&input);
254 let key = message(32);
255 for key in [None, Some(&key[..])] {
256 for (init_outlen, final_outlen) in [(32, 64), (64, 16), (16, 1), (1, 64), (15, 16)] {
257 let expected =
258 native_test_util::generichash_multipart(key, init_outlen, &parts, final_outlen)
259 .expect("libsodium final");
260 let actual =
261 ours_incremental(key, init_outlen, &parts, final_outlen).expect("final");
262 assert_eq!(actual, expected, "init {init_outlen}, final {final_outlen}");
263 }
264 for final_outlen in [0, 65] {
265 assert!(matches!(
266 ours_incremental(key, 32, &parts, final_outlen),
267 Err(Error::InvalidLength { actual, .. }) if actual == final_outlen
268 ));
269 }
270 }
271 }
272}