From e658bde37ba02cdea232d044d6f4a85be7615c20 Mon Sep 17 00:00:00 2001 From: MedzikUser Date: Tue, 28 Jun 2022 22:50:19 +0200 Subject: [PATCH] feat(sha): add hmac --- CHANGELOG.md | 2 + Cargo.lock | 18 +++++ Cargo.toml | 4 +- src/sha/mac.rs | 184 +++++++++++++++++++++++++++++++++++++++++++ src/sha/mod.rs | 20 +++++ src/{ => sha}/sha.rs | 21 +---- 6 files changed, 230 insertions(+), 19 deletions(-) create mode 100644 src/sha/mac.rs create mode 100644 src/sha/mod.rs rename src/{ => sha}/sha.rs (90%) diff --git a/CHANGELOG.md b/CHANGELOG.md index dad68b4..afdfd97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Features +- **sha**: Added HMAC Sha1, HMAC Sha256 and HMAC Sha512 ## [0.3.0] - 2022-06-23 - updated dependencies diff --git a/Cargo.lock b/Cargo.lock index 4f644c0..e28a4fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,10 +86,12 @@ dependencies = [ "anyhow", "chrono", "hex", + "hmac", "jsonwebtoken", "serde", "sha1", "sha2", + "thiserror", ] [[package]] @@ -100,6 +102,7 @@ checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -118,6 +121,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "itoa" version = "1.0.2" @@ -332,6 +344,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + [[package]] name = "syn" version = "1.0.98" diff --git a/Cargo.toml b/Cargo.toml index 88e0e11..8d30bd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,15 +13,17 @@ edition = "2021" [features] default = ["full"] full = ["sha", "jwt"] -sha = ["sha1", "sha2"] +sha = ["sha1", "sha2", "hmac"] jwt = ["chrono", "serde", "jsonwebtoken"] [dependencies] sha1 = { version = "0.10.1", optional = true } sha2 = { version = "0.10.2", optional = true } +hmac = { version = "0.12.1", optional = true } chrono = { version = "0.4.19", optional = true } serde = { version = "1.0.137", optional = true } jsonwebtoken = { version = "8.1.1", optional = true } +thiserror = "1.0.31" [dev-dependencies] anyhow = "1.0.58" diff --git a/src/sha/mac.rs b/src/sha/mac.rs new file mode 100644 index 0000000..1e03856 --- /dev/null +++ b/src/sha/mac.rs @@ -0,0 +1,184 @@ +use hmac::{Hmac, Mac}; +use sha1::Sha1; +use sha2::{Sha256, Sha512}; +use thiserror::Error; + +/// Custom error type +#[derive(Debug, Error)] +pub enum Error { + /// Invalid HMAC Key + #[error("invalid key")] + InvalidKey, +} + +/// Alias to a `Resuly` with the cutom [Error]. +pub type Result = std::result::Result; + +/// HMAC hashing algorithms +pub enum AlgorithmMac { + /// Read about HMAC in [wikipedia](https://en.wikipedia.org/wiki/HMAC) + HmacSHA1, + /// Read about HMAC in [wikipedia](https://en.wikipedia.org/wiki/HMAC) + HmacSHA256, + /// Read about HMAC in [wikipedia](https://en.wikipedia.org/wiki/HMAC) + HmacSHA512, +} + +/// Compute cryptographic hash from bytes (HMAC Sha1, HMAC Sha256, HMAC Sha512). +pub enum CryptographicMac { + /// HMAC Sha1 hasher + HmacSha1(Hmac), + /// HMAC Sha256 hasher + HmacSha256(Hmac), + /// HMAC Sha512 hasher + HmacSha512(Hmac), +} + +impl CryptographicMac { + /// Create a new HMAC Sha hasher. + /// + /// ```no_run + /// use crypto_utils::sha::{AlgorithmMac, CryptographicMac}; + /// + /// // Hmac Sha1 + /// let mut hasher = CryptographicMac::new(AlgorithmMac::HmacSHA1, b"secret").unwrap(); + /// + /// // Hmac Sha256 + /// let mut hasher = CryptographicMac::new(AlgorithmMac::HmacSHA256, b"secret").unwrap(); + /// + /// // Hmac Sha512 + /// let mut hasher = CryptographicMac::new(AlgorithmMac::HmacSHA512, b"secret").unwrap(); + /// ``` + pub fn new(algo: AlgorithmMac, key: &[u8]) -> Result { + Ok(match algo { + AlgorithmMac::HmacSHA1 => { + Self::HmacSha1(Hmac::::new_from_slice(key).map_err(|_| Error::InvalidKey)?) + } + AlgorithmMac::HmacSHA256 => Self::HmacSha256( + Hmac::::new_from_slice(key).map_err(|_| Error::InvalidKey)?, + ), + AlgorithmMac::HmacSHA512 => Self::HmacSha512( + Hmac::::new_from_slice(key).map_err(|_| Error::InvalidKey)?, + ), + }) + } + + /// Set value in the hasher + /// + /// ```no_run + /// # use crypto_utils::sha::{AlgorithmMac, CryptographicMac}; + /// # + /// # let mut hasher = CryptographicMac::new(AlgorithmMac::HmacSHA1, b"secret").unwrap(); + /// # + /// hasher.update(b"value"); + /// ``` + pub fn update(&mut self, input: &[u8]) { + match self { + // Sha1 + Self::HmacSha1(sha1) => sha1.update(input), + // Sha256 + Self::HmacSha256(sha256) => sha256.update(input), + // Sha512 + Self::HmacSha512(sha512) => sha512.update(input), + } + } + + /// Compute hash + /// + /// ```no_run + /// # use crypto_utils::sha::{AlgorithmMac, CryptographicMac}; + /// # + /// # let mut hasher = CryptographicMac::new(AlgorithmMac::HmacSHA1, b"secret").unwrap(); + /// # + /// # hasher.update(b"value"); + /// let hash: Vec = hasher.finalize(); + /// let hash_str: String = hex::encode(hash); + /// ``` + pub fn finalize(self) -> Vec { + match self { + // Sha1 + Self::HmacSha1(sha1) => sha1.finalize().into_bytes().to_vec(), + // Sha256 + Self::HmacSha256(sha256) => sha256.finalize().into_bytes().to_vec(), + // Sha512 + Self::HmacSha512(sha512) => sha512.finalize().into_bytes().to_vec(), + } + } + + /// Compute hash using a single function + /// + /// ``` + /// use crypto_utils::sha::{AlgorithmMac, CryptographicMac}; + /// + /// let hash_bytes: Vec = CryptographicMac::hash(AlgorithmMac::HmacSHA1, b"secret", b"P@ssw0rd").unwrap(); + /// + /// // decode hash to a String + /// let hash: String = hex::encode(hash_bytes); + /// + /// # assert_eq!(hash, "20bbb9ec2d4574845911b13695b776097bd46e41".to_string()) + /// ``` + pub fn hash(algo: AlgorithmMac, secret: &[u8], input: &[u8]) -> Result> { + // create hasher + let mut hasher = Self::new(algo, secret)?; + + // set value in hasher + hasher.update(input); + + // compute hash + Ok(hasher.finalize()) + } +} + +#[cfg(test)] +mod tests { + use super::{AlgorithmMac, CryptographicMac}; + + const SECRET: &[u8] = b"secret"; + const INPUT: &[u8] = b"input"; + + // expected hashes + const EXPECTED_HMAC_SHA1: &str = "30440f36ddc2809bbd4c8b1f37a6e80d7588c303"; + const EXPECTED_HMAC_SHA256: &str = + "8d8985d04b7abd32cbaa3779a3daa019e0d269a22aec15af8e7296f702cc68c6"; + const EXPECTED_HMAC_SHA512: &str = + "2ac95ed3717e042c7064a5fa7c318230cd36d85e06f8ff8373d04ca17e361629e09f46b7f151ff382a3f48c5b19121446e45c2588f0ff1de9f74b0400daef81f"; + + /// Test a HMAC Sha1 hasher + #[test] + fn hmac_sha1() { + // compute hash + let hash_bytes = CryptographicMac::hash(AlgorithmMac::HmacSHA1, SECRET, INPUT).unwrap(); + + // decode hash to a String + let hash = hex::encode(hash_bytes); + + // validate hash + assert_eq!(hash, EXPECTED_HMAC_SHA1.to_string()) + } + + /// Test a HMAC Sha256 hasher + #[test] + fn hmac_sha256() { + // compute hash + let hash_bytes = CryptographicMac::hash(AlgorithmMac::HmacSHA256, SECRET, INPUT).unwrap(); + + // decode hash to a String + let hash = hex::encode(hash_bytes); + + // validate hash + assert_eq!(hash, EXPECTED_HMAC_SHA256.to_string()) + } + + /// Test a HMAC Sha512 hasher + #[test] + fn hmac_sha512() { + // compute hash + let hash_bytes = CryptographicMac::hash(AlgorithmMac::HmacSHA512, SECRET, INPUT).unwrap(); + + // decode hash to a String + let hash = hex::encode(hash_bytes); + + // validate hash + assert_eq!(hash, EXPECTED_HMAC_SHA512.to_string()) + } +} diff --git a/src/sha/mod.rs b/src/sha/mod.rs new file mode 100644 index 0000000..783857b --- /dev/null +++ b/src/sha/mod.rs @@ -0,0 +1,20 @@ +//! Module for creating sha1, sha256 and sha512 hashes. +//! +//! ```no_run +//! use crypto_utils::sha::{Algorithm, CryptographicHash}; +//! +//! // sha1 +//! CryptographicHash::hash(Algorithm::SHA1, b"P@ssw0rd"); +//! +//! // sha256 +//! CryptographicHash::hash(Algorithm::SHA256, b"P@ssw0rd"); +//! +//! // sha512 +//! CryptographicHash::hash(Algorithm::SHA512, b"P@ssw0rd"); +//! ``` + +mod mac; +mod sha; + +pub use mac::*; +pub use sha::*; diff --git a/src/sha.rs b/src/sha/sha.rs similarity index 90% rename from src/sha.rs rename to src/sha/sha.rs index 598fd2b..b3543aa 100644 --- a/src/sha.rs +++ b/src/sha/sha.rs @@ -1,18 +1,3 @@ -//! Module for creating sha1, sha256 and sha512 hashes. -//! -//! ```no_run -//! use crypto_utils::sha::{Algorithm, CryptographicHash}; -//! -//! // sha1 -//! CryptographicHash::hash(Algorithm::SHA1, b"P@ssw0rd"); -//! -//! // sha256 -//! CryptographicHash::hash(Algorithm::SHA256, b"P@ssw0rd"); -//! -//! // sha512 -//! CryptographicHash::hash(Algorithm::SHA512, b"P@ssw0rd"); -//! ``` - use sha1::{Digest, Sha1}; use sha2::{Sha256, Sha512}; @@ -144,12 +129,12 @@ impl CryptographicHash { /// use crypto_utils::sha::{Algorithm, CryptographicHash}; /// /// // compute hash - /// let mut hash_bytes = CryptographicHash::hash(Algorithm::SHA1, b"P@ssw0rd"); + /// let hash_bytes: Vec = CryptographicHash::hash(Algorithm::SHA1, b"P@ssw0rd"); /// /// // decode hash to a String - /// let hash = hex::encode(hash_bytes); + /// let hash: String = hex::encode(hash_bytes); /// - /// assert_eq!(hash, "21bd12dc183f740ee76f27b78eb39c8ad972a757".to_string()) + /// # assert_eq!(hash, "21bd12dc183f740ee76f27b78eb39c8ad972a757".to_string()) /// ``` pub fn hash(algo: Algorithm, input: &[u8]) -> Vec { // create hasher