extrapaytr-crypto 0.1.0

Digest, HMAC, constant-time comparison and at-rest sealing helpers for ExtraPayTR
Documentation
use hmac::{Hmac, Mac};
use sha2::{Sha256, Sha512};

#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum CryptoError {
    #[error("HMAC key has an invalid length")]
    InvalidKeyLength,
}

/// Computes `HMAC-SHA256(key, message)`, returning the raw 32-byte digest.
pub fn hmac_sha256(key: &[u8], message: &[u8]) -> Result<[u8; 32], CryptoError> {
    let mut mac =
        <Hmac<Sha256> as Mac>::new_from_slice(key).map_err(|_| CryptoError::InvalidKeyLength)?;
    mac.update(message);
    Ok(mac.finalize().into_bytes().into())
}

/// Computes `HMAC-SHA512(key, message)`, returning the raw 64-byte digest.
pub fn hmac_sha512(key: &[u8], message: &[u8]) -> Result<[u8; 64], CryptoError> {
    let mut mac =
        <Hmac<Sha512> as Mac>::new_from_slice(key).map_err(|_| CryptoError::InvalidKeyLength)?;
    mac.update(message);
    Ok(mac.finalize().into_bytes().into())
}

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

    // RFC 4231 test case 1.
    #[test]
    fn hmac_sha256_rfc4231_case1() {
        let key = [0x0bu8; 20];
        let data = b"Hi There";
        let expected =
            hex::decode("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7")
                .unwrap();
        assert_eq!(hmac_sha256(&key, data).unwrap().to_vec(), expected);
    }

    // RFC 4231 test case 2.
    #[test]
    fn hmac_sha256_rfc4231_case2() {
        let key = b"Jefe";
        let data = b"what do ya want for nothing?";
        let expected =
            hex::decode("5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843")
                .unwrap();
        assert_eq!(hmac_sha256(key, data).unwrap().to_vec(), expected);
    }

    // RFC 4231 test case 6 (key longer than block size).
    #[test]
    fn hmac_sha256_rfc4231_case6() {
        let key = [0xaau8; 131];
        let data = b"Test Using Larger Than Block-Size Key - Hash Key First";
        let expected =
            hex::decode("60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54")
                .unwrap();
        assert_eq!(hmac_sha256(&key, data).unwrap().to_vec(), expected);
    }

    // RFC 4231 test case 1 for HMAC-SHA512.
    #[test]
    fn hmac_sha512_rfc4231_case1() {
        let key = [0x0bu8; 20];
        let data = b"Hi There";
        let expected = hex::decode(
            "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854",
        )
        .unwrap();
        assert_eq!(hmac_sha512(&key, data).unwrap().to_vec(), expected);
    }

    #[test]
    fn empty_key_is_accepted_per_hmac_semantics() {
        // HMAC pads/hashes the key regardless of length; an empty key is
        // unusual but well-defined, not an error.
        assert!(hmac_sha256(&[], b"message").is_ok());
    }
}