Skip to main content

bouncycastle_hmac/
lib.rs

1//! This crate contains an implementation of the Hash-Based Message Authentication Code (HMAC)
2//! as specified in RFC2104, taking into account NIST Implementation Guidance in FIPS 140-2 IG A.8
3//! and NIST SP 800-107-r1.
4//!
5//! # Usage
6//!
7//! The HMAC object (and the [MAC] trait in general) is designed in three phases:
8//!
9//! * The initialization phase where you specify the underlying hash function and the key material.
10//! * The update phase where you feed in the content being MAC'd, either in one-shot or in chunks.
11//! * The finalization phase where you either obtain the MAC value or verify an existing MAC value.
12//!
13//! The initialization phase is primarily performed via the [MAC::new] function which performs
14//! checks on the provided key to ensure that it is of the correct type [KeyType::MACKey] and tagged
15//! at the correct security level for the chosen hash function. In cases where you need to use HMAC
16//! with an intentially week key (such as an all-zero salt), the alternative constructor
17//! [MAC::new_allow_weak_key] can be used.
18//!
19//! The update phase supports streaming of the content via the repeated calls to the [MAC::do_update] function.
20//! One-shot APIs are provided that combine the update and finalization phases into a single function call.
21//!
22//!
23//! # Examples
24//!
25//! HMAC objects can be constructed with any underlying hash function that implements [Hash].
26//! Type aliases are provided for the common HMAC-HASH algorithms.
27//!
28//! The following object instantiations are equivalent:
29//!
30//! ```
31//! use bouncycastle_hmac::HMAC_SHA256;
32//! use bouncycastle_core::traits::MAC;
33//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType};
34//!
35//! let key = KeyMaterial256::from_bytes_as_type(
36//!             b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
37//!             KeyType::MACKey).unwrap();
38//!
39//! let hmac = HMAC_SHA256::new(&key).expect("Should succeed because key is long enough and tagged KeyType::MACKey");
40//! ```
41//! and
42//! ```
43//! use bouncycastle_hmac::HMAC;
44//! use bouncycastle_sha2::SHA256;
45//! use bouncycastle_core::traits::MAC;
46//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType};
47//!
48//! let key = KeyMaterial256::from_bytes_as_type(
49//!             b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
50//!             KeyType::MACKey).unwrap();
51//!
52//! let hmac = HMAC::<SHA256>::new(&key).expect("Should succeed because key is long enough and tagged KeyType::MACKey");
53//! ```
54//!
55//! ## Computing a MAC
56//! MAC functionality is accessed via the [MAC] trait.
57//!
58//! The simplest usage is via the one-shot functions.
59//! ```
60//! use bouncycastle_hmac::HMAC_SHA256;
61//! use bouncycastle_core::traits::MAC;
62//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType};
63//!
64//! let key = KeyMaterial256::from_bytes_as_type(
65//!             b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
66//!             KeyType::MACKey).unwrap();
67//!
68//! let data: &[u8] = b"Hello, world!";
69//! let hmac = HMAC_SHA256::new(&key).expect("Should succeed because key is long enough and tagged KeyType::MACKey");
70//! let output: Vec<u8> = hmac.mac(data);
71//! ```
72//!
73//! More advanced usage will require creating an HMAC object to hold state between successive calls,
74//! for example if input is received in chunks and not all available at the same time:
75//!
76//! ```
77//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType};
78//! use bouncycastle_core::traits::MAC;
79//! use bouncycastle_hmac::HMAC_SHA256;
80//!
81//! let key = KeyMaterial256::from_bytes_as_type(
82//!             b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
83//!             KeyType::MACKey).unwrap();
84//! let mut hmac = HMAC_SHA256::new(&key).expect("Should succeed because key is long enough and tagged KeyType::MACKey");
85//! hmac.do_update(b"Hello,");
86//! hmac.do_update(b" world!");
87//! let output: Vec<u8> = hmac.do_final();
88//! ```
89//!
90//! ## Verifying a MAC
91//! MAC functionality is accessed via the [MAC] trait which provides functions for MAC verification.
92//! The built-in verification functions use constant-time comparisons and so are *strongly recommended*
93//! rather than re-computing the MAC value and comparing it yourself.
94//!
95//! The simplest usage is via the one-shot functions.
96//! ```
97//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType};
98//! use bouncycastle_core::traits::MAC;
99//!
100//! let key = KeyMaterial256::from_bytes_as_type(
101//!             b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
102//!             KeyType::MACKey).unwrap();
103//! let data: &[u8] = b"Hello, world!";
104//!
105//! // .verify() returns a bool: true if the MAC is valid, false otherwise.
106//! if bouncycastle_hmac::HMAC_SHA256::new(&key).unwrap()
107//!                 .verify(data,
108//!                         b"\xa2\xd1\x2e\xcf\xfc\x41\xba\xf1\x23\xd6\x3e\x44\xfc\x27\x88\x90\x47\xcd\x08\xe7\x05\xd7\x0f\xa3\xb8\xaa\x8a\x5c\x18\x7c\x6c\xa9"
109//!                         )
110//! {
111//!     println!("MAC is valid!");
112//! } else {
113//!     println!("MAC is invalid!");
114//! }
115//! ```
116//!
117//! Similarly, a streaming version is available, which is identical to the streaming interface for
118//! computing a mac value, but calls [MAC::do_verify_final] instead of [MAC::do_final].
119//!
120//! ```
121//! use bouncycastle_core::key_material::{KeyMaterial256, KeyType};
122//! use bouncycastle_core::traits::MAC;
123//! use bouncycastle_hmac::HMAC_SHA256;
124//!
125//! let key = KeyMaterial256::from_bytes_as_type(
126//!             b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
127//!             KeyType::MACKey).unwrap();
128//! let mut hmac = HMAC_SHA256::new(&key).unwrap();
129//! hmac.do_update(b"Hello,");
130//! hmac.do_update(b" world!");
131//! if hmac.do_verify_final(b"\xa2\xd1\x2e\xcf\xfc\x41\xba\xf1\x23\xd6\x3e\x44\xfc\x27\x88\x90\x47\xcd\x08\xe7\x05\xd7\x0f\xa3\xb8\xaa\x8a\x5c\x18\x7c\x6c\xa9"
132//!                     )
133//! {
134//!     println!("MAC is valid!");
135//! } else {
136//!     println!("MAC is invalid!");
137//! }
138//! ```
139//!
140//! # Request for feedback on fallability of this API
141//! We have made an effort to reduce the number of fallibly APIs in the [MAC] trait; in particular
142//! the APIs for the update and most of the finalize phases are infallible -- they just work.
143//! However, we were not able to design it to completely avoid runtime error conditions, such as
144//! providing [MAC::do_final_out] with a buffer that is too small to hold what NIST considered the smallest
145//! valid MAC value for the given underlying hash function.
146//!
147//! Also, the key strength and type checking in the initialization phase means that both [MAC::new] and
148//! [MAC::new_allow_weak_key] have several runtime error conditions that they check for.
149//!
150//! All of this leads to application code that requires a lot more .unwrap(), .expect(), or match than we would
151//! really like.
152//!
153//! We would love feedback on an alternate design for this API than carries less runtime error
154//! conditions, without sacrificing the key strength checking that the metadata in the [KeyMaterialTrait] object allows.
155
156#![forbid(unsafe_code)]
157#![allow(incomplete_features)] // because at time of writing, generic_const_exprs is not a stable feature
158#![feature(generic_const_exprs)]
159
160use bouncycastle_core::errors::{KeyMaterialError, MACError};
161use bouncycastle_core::key_material::{KeyMaterialTrait, KeyType};
162use bouncycastle_core::traits::{Algorithm, Hash, MAC, SecurityStrength};
163use bouncycastle_sha2::{SHA224, SHA256, SHA384, SHA512};
164use bouncycastle_sha3::{SHA3_224, SHA3_256, SHA3_384, SHA3_512};
165use bouncycastle_utils::ct;
166
167/*** String constants ***/
168pub const HMAC_SHA224_NAME: &str = "HMAC-SHA224";
169pub const HMAC_SHA256_NAME: &str = "HMAC-SHA256";
170pub const HMAC_SHA384_NAME: &str = "HMAC-SHA384";
171pub const HMAC_SHA512_NAME: &str = "HMAC-SHA512";
172pub const HMAC_SHA3_224_NAME: &str = "HMAC-SHA3-224";
173pub const HMAC_SHA3_256_NAME: &str = "HMAC-SHA3-256";
174pub const HMAC_SHA3_384_NAME: &str = "HMAC-SHA3-384";
175pub const HMAC_SHA3_512_NAME: &str = "HMAC-SHA3-512";
176
177/*** Type aliases ***/
178#[allow(non_camel_case_types)]
179pub type HMAC_SHA224 = HMAC<SHA224>;
180impl Algorithm for HMAC_SHA224 {
181    const ALG_NAME: &'static str = HMAC_SHA224_NAME;
182    const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit;
183}
184
185#[allow(non_camel_case_types)]
186pub type HMAC_SHA256 = HMAC<SHA256>;
187impl Algorithm for HMAC_SHA256 {
188    const ALG_NAME: &'static str = HMAC_SHA256_NAME;
189    const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit;
190}
191
192#[allow(non_camel_case_types)]
193pub type HMAC_SHA384 = HMAC<SHA384>;
194impl Algorithm for HMAC_SHA384 {
195    const ALG_NAME: &'static str = HMAC_SHA384_NAME;
196    const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit;
197}
198
199#[allow(non_camel_case_types)]
200pub type HMAC_SHA512 = HMAC<SHA512>;
201impl Algorithm for HMAC_SHA512 {
202    const ALG_NAME: &'static str = HMAC_SHA512_NAME;
203    const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit;
204}
205
206#[allow(non_camel_case_types)]
207pub type HMAC_SHA3_224 = HMAC<SHA3_224>;
208impl Algorithm for HMAC_SHA3_224 {
209    const ALG_NAME: &'static str = HMAC_SHA3_224_NAME;
210    const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_112bit;
211}
212
213#[allow(non_camel_case_types)]
214pub type HMAC_SHA3_256 = HMAC<SHA3_256>;
215impl Algorithm for HMAC_SHA3_256 {
216    const ALG_NAME: &'static str = HMAC_SHA3_256_NAME;
217    const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit;
218}
219
220#[allow(non_camel_case_types)]
221pub type HMAC_SHA3_384 = HMAC<SHA3_384>;
222impl Algorithm for HMAC_SHA3_384 {
223    const ALG_NAME: &'static str = HMAC_SHA3_384_NAME;
224    const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit;
225}
226
227#[allow(non_camel_case_types)]
228pub type HMAC_SHA3_512 = HMAC<SHA3_512>;
229impl Algorithm for HMAC_SHA3_512 {
230    const ALG_NAME: &'static str = HMAC_SHA3_512_NAME;
231    const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit;
232}
233
234// TODO: is there a rustacious way to extract this from HASH?
235const LARGEST_HASHER_OUTPUT_LEN: usize = 64;
236
237// HMAC implements RFC 2104.
238#[derive(Clone)]
239pub struct HMAC<HASH: Hash + Default> {
240    hasher: HASH,
241    key: [u8; LARGEST_HASHER_OUTPUT_LEN],
242    key_len: usize, // Doing it this way to avoid needing a vec, so that this can be made no_std friendly.
243}
244
245// See definitions in RFC 2104 Section 2.
246const IPAD_BYTE: u8 = 0x36;
247const OPAD_BYTE: u8 = 0x5C;
248
249// Per FIPS 140-2 IG A.8 Use of a truncated HMAC (matching NIST SP 800-107-r1
250// Section 5.3.3. Truncation of HMAC), says that the minimum truncation of a
251// HMAC for tagging should be 32 bits; this exceeds the lower bound set by
252// IETF RFC 2104 Section 5 Truncated output, which sets the lower bound to be
253// half of the hash's length and no fewer than 80 bits.
254//
255// However, as we feel there should be a minimum limit (and have an author
256// work around this via explicit truncation manually afterwards), but not
257// be too strict about it,
258pub const MIN_FIPS_DIGEST_LEN: usize = 4; // 32 / 8;
259
260impl<HASH: Hash + Default> HMAC<HASH> {
261    fn pad_key_into_hasher(&mut self, padding: u8) {
262        // TODO: it would be nice to be able to statically extract the length of HASH and not need a Vec or over-sized array here.
263        // TODO: make this no_std-friendly
264        let mut padded = vec![0u8; self.hasher.block_bitlen() / 8];
265
266        // Per RFC 2104 Section 2, if the application key exceeds the block
267        // length of the underlying hashes algorithm, we apply a hash invocation
268        // over the key first.
269        // if self.key_len > self.hasher.block_bitlen() / 8 {
270        //     HASH::default().hash_out(&self.key[..self.key_len], &mut padded[..self.hasher.output_len()])?;
271        // } else {
272        // TODO: does this need a guard for a key_len longer than the block length?
273        padded[..self.key_len].copy_from_slice(&self.key[..self.key_len]);
274        // }
275
276        // XXX: easier way to xor over Vec?
277        for entry in &mut padded {
278            *entry ^= padding;
279        }
280
281        // Per RFC 2104 Section 2, write the padded key into the stream prior
282        // to any other data.
283        self.hasher.do_update(&padded)
284    }
285
286    /// Private init so that users are forced to go through one of the public new methods and thus we
287    /// don't need to track state errors.
288    fn init(&mut self, key: &impl KeyMaterialTrait, allow_weak_keys: bool) -> Result<(), MACError> {
289        // check that the key is of type KeyMaterial::MACKey
290        // Make an exception for all-zero keys, which is allowed (which can be zero-length or non-zero-length,
291        // because it's just a nuisance to force users to set KeyType::MACKey for an all-zero key.
292        if !(key.key_type() == KeyType::Zeroized || key.key_type() == KeyType::MACKey) {
293            return Err(MACError::KeyMaterialError(KeyMaterialError::InvalidKeyType(
294                "Key type must be a MAC key.",
295            )));
296        }
297
298        // import the key material as bytes.
299        // Per RFC 2104 Section 2, if the application key exceeds the block
300        // length of the underlying hashes algorithm, we apply a hash invocation
301        // over the key first.
302
303        if key.key_len() > self.hasher.block_bitlen() / 8 {
304            // then we have to pre-hash it -- use a new instance of the hasher rather than the internal one
305            HASH::default().hash_out(key.ref_to_bytes(), &mut self.key[..self.hasher.output_len()]);
306            self.key_len = self.hasher.output_len();
307        } else {
308            self.key[..key.key_len()].copy_from_slice(key.ref_to_bytes());
309            self.key_len = key.key_len();
310        }
311
312        self.pad_key_into_hasher(IPAD_BYTE);
313
314        // check that the key had enough security level
315        if !allow_weak_keys && key.security_strength() < HASH::default().max_security_strength() {
316            Err(KeyMaterialError::SecurityStrength(
317                "HMAC::init(): provided key has a lower security strength than the instantiated HMAC",
318            ))?
319        } else {
320            Ok(())
321        }
322    }
323
324    /// the out buffer can be oversized, but not less than the MIN_FIPS_DIGEST_LENGTH
325    /// Returns the number of bytes written.
326    fn do_final_internal_out(mut self, out: &mut [u8]) -> Result<usize, MACError> {
327        if out.len() < MIN_FIPS_DIGEST_LEN {
328            return Err(MACError::InvalidLength(
329                "HMAC truncation too short for FIPS 140-2 guidelines",
330            ));
331        }
332
333        // Per RFC 2104 Section 2, save our inner digest to calculate our
334        // outer digest. Note that we can't (necessarily) reuse out as a
335        // scratch pad here: if we're truncating the output but not
336        // truncating the underlying hashes, we'd lose bytes and compute an
337        // invalid outer hashes.
338        // TODO: rework this to be no_std friendly (ie no vec!)
339        let mut ihash = vec![0u8; self.hasher.output_len()];
340        self.hasher.do_final_out(&mut ihash);
341
342        // ohash
343        self.hasher = HASH::default();
344        self.pad_key_into_hasher(OPAD_BYTE);
345        self.hasher.do_update(&ihash);
346        Ok(self.hasher.do_final_out(out))
347    }
348}
349
350// TODO: potential feature: add an interface that pre-computes the intermediate values (K XOR ipad) and (K XOR opad)
351// TODO for a given key as described in RFC2104 section 4.
352// TODO: This is essentially a "batch mode" where you want to perform many MACs or Verifications with the same key
353// TODO: against different data.
354
355impl<HASH: Hash + Default> MAC for HMAC<HASH> {
356    fn new(key: &impl KeyMaterialTrait) -> Result<Self, MACError> {
357        let mut hmac =
358            Self { hasher: HASH::default(), key: [0u8; LARGEST_HASHER_OUTPUT_LEN], key_len: 0 };
359        hmac.init(key, false)?;
360        Ok(hmac)
361    }
362
363    fn new_allow_weak_key(key: &impl KeyMaterialTrait) -> Result<Self, MACError> {
364        let mut hmac =
365            Self { hasher: HASH::default(), key: [0u8; LARGEST_HASHER_OUTPUT_LEN], key_len: 0 };
366        hmac.init(key, true)?;
367        Ok(hmac)
368    }
369
370    fn output_len(&self) -> usize {
371        self.hasher.output_len()
372    }
373
374    fn mac(self, data: &[u8]) -> Vec<u8> {
375        let mut out = vec![0_u8; self.hasher.output_len()];
376        let bytes_written = self.mac_out(data, &mut out).expect("HMAC::mac(): should not have failed because we gave it a sufficiently large output buffer to meet FIPS rules.");
377        out[..bytes_written].to_vec()
378    }
379
380    fn mac_out(mut self, data: &[u8], mut out: &mut [u8]) -> Result<usize, MACError> {
381        self.do_update(data);
382        self.do_final_out(&mut out)
383    }
384
385    fn verify(mut self, data: &[u8], mac: &[u8]) -> bool {
386        self.do_update(data);
387        self.do_verify_final(mac)
388    }
389
390    fn do_update(&mut self, data: &[u8]) {
391        self.hasher.do_update(data)
392    }
393
394    fn do_final(self) -> Vec<u8> {
395        let mut out = vec![0_u8; self.hasher.output_len()];
396        self.do_final_internal_out(&mut out).expect("HMAC::do_final(): should not have failed because we gave it a sufficiently large output buffer to meet FIPS rules.");
397        out
398    }
399
400    fn do_final_out(self, mut out: &mut [u8]) -> Result<usize, MACError> {
401        self.do_final_internal_out(&mut out)
402    }
403
404    fn do_verify_final(self, mac: &[u8]) -> bool {
405        let mut out = vec![0_u8; HASH::default().output_len()];
406        let output_len = self.do_final_internal_out(&mut out).expect("HMAC::do_final(): should not have failed because we gave it a sufficiently large output buffer to meet FIPS rules.");
407        if mac.len() != output_len {
408            return false;
409        }
410        ct::ct_eq_bytes(mac, &out[..output_len])
411    }
412
413    fn max_security_strength(&self) -> SecurityStrength {
414        HASH::default().max_security_strength()
415    }
416}