libvctrl_sha512 3.2.0

Zero-dependency SHA512, HMAC-SHA512, HKDF-SHA512, and optional SHA384
Documentation
#![cfg_attr(not(test), no_std)]
#![allow(clippy::arithmetic_side_effects)]

#[macro_export]
macro_rules! impl_hmac {
    ($hash_struct:ty, $output_size:expr, $block_size:expr) => {
        #[doc = concat!("HMAC keyed-hash implementation using `", stringify!($hash_struct), "`.")]
        pub struct HMAC {
            ih: Option<$hash_struct>,
            padded: [u8; $block_size],
        }

        impl core::fmt::Debug for HMAC {
            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
                f.write_str("HMAC")
            }
        }

        impl zeroize::Zeroize for HMAC {
            fn zeroize(&mut self) {
                if let Some(ref mut ih) = self.ih {
                    zeroize::Zeroize::zeroize(ih);
                }
                zeroize::Zeroize::zeroize(&mut self.padded);
            }
        }

        impl Drop for HMAC {
            fn drop(&mut self) {
                zeroize::Zeroize::zeroize(self);
            }
        }

        #[allow(clippy::indexing_slicing)]
        impl HMAC {
            fn prepare_key(key: &[u8]) -> [u8; $block_size] {
                let mut block_key = [0_u8; $block_size];
                if key.len() > $block_size {
                    let hash = zeroize::Zeroizing::new(<$hash_struct>::hash(key));
                    let hash_bytes = &*hash;
                    block_key[..$output_size].copy_from_slice(&hash_bytes[..$output_size]);
                } else {
                    block_key[..key.len()].copy_from_slice(key);
                }
                block_key
            }

            #[doc = "One-shot HMAC computation."]
            #[must_use]
            pub fn mac<T: AsRef<[u8]>, U: AsRef<[u8]>>(input: T, key: U) -> [u8; $output_size] {
                let mut hmac = Self::new(key);
                hmac.update(input);
                hmac.finalize()
            }

            #[doc = "Creates a new HMAC context from a secret key."]
            #[must_use]
            pub fn new(key: impl AsRef<[u8]>) -> Self {
                let key = key.as_ref();
                let mut block_key = Self::prepare_key(key);
                let mut padded = [0x36_u8; $block_size];
                for (padded_byte, block_byte) in padded.iter_mut().zip(block_key.iter()) {
                    *padded_byte ^= *block_byte;
                }
                let mut ih = <$hash_struct>::new();
                ih.update(&padded);
                zeroize::Zeroize::zeroize(&mut block_key);
                HMAC {
                    ih: Some(ih),
                    padded,
                }
            }

            #[doc = "Feeds data into the HMAC."]
            pub fn update(&mut self, input: impl AsRef<[u8]>) {
                if let Some(ref mut ih) = self.ih {
                    ih.update(input);
                }
            }

            #[doc = "Finalizes the HMAC and returns the authentication tag."]
            #[must_use]
            pub fn finalize(mut self) -> [u8; $output_size] {
                for padded_byte in self.padded.iter_mut() {
                    *padded_byte ^= 0x6a;
                }
                let mut oh = <$hash_struct>::new();
                oh.update(&self.padded);
                let inner = zeroize::Zeroizing::new(
                    self.ih
                        .take()
                        .unwrap_or_else(|| <$hash_struct>::new())
                        .finalize(),
                );
                oh.update(&*inner);
                oh.finalize()
            }

            #[doc = "Finalizes the HMAC and verifies the tag against `expected` in constant-ish time."]
            #[inline]
            #[must_use]
            pub fn finalize_verify(self, expected: &[u8; $output_size]) -> bool {
                let out = self.finalize();
                $crate::utils::verify(&out, expected)
            }

            #[doc = "One-shot HMAC computation with verification."]
            #[inline]
            #[must_use]
            pub fn verify<T: AsRef<[u8]>, U: AsRef<[u8]>>(
                input: T,
                key: U,
                expected: &[u8; $output_size],
            ) -> bool {
                let mac = Self::mac(input, key);
                $crate::utils::verify(&mac, expected)
            }
        }
    };
}

#[macro_export]
macro_rules! impl_hkdf {
    ($hash_struct:ty, $output_size:expr, $block_size:expr) => {
        #[doc = concat!("HKDF key derivation using `", stringify!($hash_struct), "`.")]
        #[derive(Debug, Copy, Clone)]
        pub struct HKDF;

        #[allow(clippy::indexing_slicing)]
        impl HKDF {
            #[doc = "HKDF-Extract step. Returns a pseudorandom key (PRK)."]
            #[inline]
            #[must_use]
            pub fn extract(salt: impl AsRef<[u8]>, ikm: impl AsRef<[u8]>) -> [u8; $output_size] {
                HMAC::mac(ikm, salt)
            }

            #[doc = "HKDF-Expand step. Fills `out` with output keying material."]
            #[inline]
            pub fn expand(out: &mut [u8], prk: impl AsRef<[u8]>, info: impl AsRef<[u8]>) {
                let prk = prk.as_ref();
                assert_eq!(
                    prk.len(),
                    $output_size,
                    "HKDF expects a {}-byte PRK",
                    $output_size
                );
                let info = info.as_ref();
                let max_len = 255_usize.saturating_mul($output_size);
                assert!(
                    out.len() <= max_len,
                    "Requested output exceeds RFC 5869 limit"
                );
                let mut offset = 0_usize;
                let mut counter: u32 = 1;
                while offset < out.len() {
                    let mut hmac = HMAC::new(prk);
                    if offset != 0 {
                        if let Some(prev) = out.get(offset.saturating_sub($output_size)..offset) {
                            hmac.update(prev);
                        }
                    }
                    hmac.update(info);
                    let counter_byte = u8::try_from(counter).unwrap_or(0);
                    hmac.update([counter_byte]);
                    let block = zeroize::Zeroizing::new(hmac.finalize());
                    let left = core::cmp::min($output_size, out.len().saturating_sub(offset));
                    if let Some(dst) = out.get_mut(offset..offset.saturating_add(left)) {
                        if let Some(src) = block.get(..left) {
                            dst.copy_from_slice(src);
                        }
                    }
                    offset = offset.saturating_add($output_size);
                    counter = counter.wrapping_add(1);
                }
            }
        }
    };
}

pub mod hkdf;
pub mod hmac;
pub mod sha512;
pub mod utils;

#[cfg(feature = "sha384")]
pub mod sha384;

pub use hkdf::HKDF;
pub use hmac::HMAC;
pub use sha512::Hash;
pub use utils::{BLOCKBYTES, BYTES};

#[cfg(test)]
mod tests {
    use super::*;
    use criterion as _;

    #[test]
    fn hmac_vectors() {
        let h = HMAC::mac([], [0_u8; 32]);
        let expected: [u8; 64] = [
            185, 54, 206, 232, 108, 159, 135, 170, 93, 60, 111, 46, 132, 203, 90, 66, 57, 165, 254,
            80, 72, 10, 110, 198, 107, 112, 171, 91, 31, 74, 198, 115, 12, 108, 81, 84, 33, 179,
            39, 236, 29, 105, 64, 46, 83, 223, 180, 154, 215, 56, 30, 176, 103, 179, 56, 253, 123,
            12, 178, 34, 71, 34, 93, 71,
        ];
        assert_eq!(h, expected);
        assert!(HMAC::verify([], [0_u8; 32], &expected));

        let h = HMAC::mac([42_u8; 69], []);
        let expected: [u8; 64] = [
            56, 224, 189, 205, 65, 104, 107, 85, 241, 188, 253, 35, 238, 174, 69, 191, 206, 183,
            205, 71, 196, 180, 56, 122, 106, 55, 136, 7, 208, 183, 99, 67, 229, 213, 255, 154, 107,
            136, 11, 154, 11, 187, 75, 214, 172, 117, 14, 248, 189, 48, 193, 62, 37, 208, 159, 227,
            115, 59, 54, 91, 143, 143, 254, 220,
        ];
        assert_eq!(h, expected);
        assert!(HMAC::verify([42_u8; 69], [], &expected));
    }

    #[test]
    fn hkdf_vector() {
        let ikm = [0x0b_u8; 22];
        let salt: [u8; 13] = [
            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
        ];
        let info: [u8; 10] = [0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9];
        let expected: [u8; 42] = [
            0x83, 0x23, 0x90, 0x08, 0x6c, 0xda, 0x71, 0xfb, 0x47, 0x62, 0x5b, 0xb5, 0xce, 0xb1,
            0x68, 0xe4, 0xc8, 0xe2, 0x6a, 0x1a, 0x16, 0xed, 0x34, 0xd9, 0xfc, 0x7f, 0xe9, 0x2c,
            0x14, 0x81, 0x57, 0x93, 0x38, 0xda, 0x36, 0x2c, 0xb8, 0xd9, 0xf9, 0x25, 0xd7, 0xcb,
        ];
        let prk = HKDF::extract(salt, ikm);
        let mut okm = [0_u8; 42];
        HKDF::expand(&mut okm, prk, info);
        assert_eq!(okm, expected);
    }
}