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