Skip to content

Commit

Permalink
Rust: Add ring as a feature
Browse files Browse the repository at this point in the history
- Fixed import errors for `use hmac_serialiser::Algorithm`
- Changed import for errors to `use hmac_serialiser::Error` from `use hmac_serialiser::errors::Error`
- Added `ring` crate as a feature in `Cargo.toml` for those who prefers using ring over RustCrypto
  • Loading branch information
KJHJason committed Jun 27, 2024
1 parent 2bd02c3 commit 0baf5ef
Show file tree
Hide file tree
Showing 7 changed files with 379 additions and 272 deletions.
31 changes: 24 additions & 7 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "hmac-serialiser"
version = "0.2.0"
version = "0.3.0"
description = "HMAC Serialisers to cryptographically sign data like Python's ItsDangerous library but in rust."
authors = [
"KJHJason <[email protected]>",
Expand All @@ -11,14 +11,31 @@ repository = "https://github.com/KJHJason/hmac-serialiser/tree/master/rust"
license = "MIT"
edition = "2021"

[features]
default = []
rust_crypto = ["sha1", "sha2", "hkdf", "hmac"]
ring = ["dep:ring"]

[dependencies]
rand = "0.8.5"
sha2 = "0.10.8"
hmac = "0.12.1"
sha1 = "0.10.6"
hkdf = "0.12.4"
sha1 = { version = "0.10.6", optional = true }
sha2 = { version = "0.10.8", optional = true }
hkdf = { version = "0.12.4", optional = true }
hmac = { version = "0.12.1", optional = true }
ring = { version = "0.17.8", optional = true }
base64 = "0.22.1"
chrono = { version = "0.4.38", features = ["serde"] }
serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.117"
chrono = "0.4.38"
thiserror = "1.0.61"

[dev-dependencies]
rand = "0.8.5"
chrono = { version = "0.4.38", features = ["serde"] }

[[test]]
path = "tests/hmac_random.rs"
name = "hmac_random"

[[test]]
path = "tests/hkdf_random.rs"
name = "hkdf_random"
12 changes: 11 additions & 1 deletion rust/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,22 @@ This responsibility is instead placed on the developer's hands. Hence, removing
Additionally, the key used in the HMAC algorithm is expanded using HKDF to address key reuse issues by deriving the key from the original key, salt, and an optional info supplied.
Moreover, the expanded key is expanded to the length of the hash function's output size that is used in the HMAC algorithm to avoid key padding which can reduce the efforts needed to brute-force.

Regarding the cryptographic implementations, the underlying [SHA1](https://crates.io/crates/sha1), [SHA2](https://crates.io/crates/sha2), [HMAC](https://crates.io/crates/hmac), and [HKDF](https://crates.io/crates/hkdf) implementations are by [RustCrypto](https://github.com/RustCrypto).
Regarding the cryptographic implementations, you can choose which implementations to use from via the `features` flag in the `Cargo.toml` file:
- `rust_crypto`
- the underlying [SHA1](https://crates.io/crates/sha1), [SHA2](https://crates.io/crates/sha2), [HMAC](https://crates.io/crates/hmac), and [HKDF](https://crates.io/crates/hkdf) implementations are by [RustCrypto](https://github.com/RustCrypto).
- `ring`
- The underlying SHA1, SHA2, HMAC, and HKDF implementations are from the [ring](https://crates.io/crates/ring) crate.

Additionally, the data serialisation and deserialisation uses the [serde](https://crates.io/crates/serde) crate and the signed data is then encoded or decoded using the [base64](https://crates.io/crates/base64) crate.

## Sample Usage

Add this to your `Cargo.toml`:
```toml
[dependencies]
hmac-serialiser = { version = "0.3.0", features = ["rust_crypto"] }
```

```rust
use hmac_serialiser::{Encoder, HmacSigner, KeyInfo, Payload, Algorithm};
use serde::{Serialize, Deserialize};
Expand Down
32 changes: 26 additions & 6 deletions rust/src/algorithm.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use sha1::Sha1;
use sha2::{Digest, Sha256, Sha384, Sha512};
#[cfg(feature = "ring")]
use ring::{hkdf, hmac};

#[derive(Default, Clone, Debug)]
pub enum Algorithm {
Expand All @@ -14,10 +14,30 @@ impl Algorithm {
#[inline]
pub fn output_length(&self) -> usize {
match self {
Algorithm::SHA1 => Sha1::output_size(),
Algorithm::SHA256 => Sha256::output_size(),
Algorithm::SHA384 => Sha384::output_size(),
Algorithm::SHA512 => Sha512::output_size(),
Algorithm::SHA1 => 20,
Algorithm::SHA256 => 32,
Algorithm::SHA384 => 48,
Algorithm::SHA512 => 64,
}
}

#[cfg(feature = "ring")]
pub fn to_hmac(&self) -> hmac::Algorithm {
match self {
Algorithm::SHA1 => hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY,
Algorithm::SHA256 => hmac::HMAC_SHA256,
Algorithm::SHA384 => hmac::HMAC_SHA384,
Algorithm::SHA512 => hmac::HMAC_SHA512,
}
}

#[cfg(feature = "ring")]
pub fn to_hkdf(&self) -> hkdf::Algorithm {
match self {
Algorithm::SHA1 => hkdf::HKDF_SHA1_FOR_LEGACY_USE_ONLY,
Algorithm::SHA256 => hkdf::HKDF_SHA256,
Algorithm::SHA384 => hkdf::HKDF_SHA384,
Algorithm::SHA512 => hkdf::HKDF_SHA512,
}
}
}
105 changes: 19 additions & 86 deletions rust/src/hkdf.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
use crate::algorithm::Algorithm;

#[cfg(feature = "rust_crypto")]
use hkdf::Hkdf;

#[cfg(feature = "ring")]
use ring::hkdf;

pub struct HkdfWrapper {
algo: Algorithm,
}

#[cfg(feature = "rust_crypto")]
macro_rules! hkdf_expand {
($self:ident, $ikm:ident, $salt:ident, $info:ident, $D:ty) => {{
let hk = Hkdf::<$D>::new(Some($salt), $ikm);
Expand All @@ -20,6 +26,7 @@ impl HkdfWrapper {
Self { algo }
}

#[cfg(feature = "rust_crypto")]
pub fn expand(&self, ikm: &[u8], salt: &[u8], info: &[u8]) -> Vec<u8> {
match self.algo {
Algorithm::SHA1 => hkdf_expand!(self, ikm, salt, info, sha1::Sha1),
Expand All @@ -28,92 +35,18 @@ impl HkdfWrapper {
Algorithm::SHA512 => hkdf_expand!(self, ikm, salt, info, sha2::Sha512),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::algorithm::Algorithm;
use rand;
use rand::Rng as _;

pub fn get_random_bytes(length: usize) -> Vec<u8> {
let mut random_bytes = vec![0u8; length];
rand::thread_rng().fill(&mut random_bytes[..]);
random_bytes
}

fn bytes_to_hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}

#[test]
fn test_empty_key_hkdf_expand() {
let salt = b"";
let ikm = b"";
let info = b"";
let algo = Algorithm::SHA1;
let algo_output_length = algo.output_length();
let hkdf = HkdfWrapper::new(algo);
let okm = hkdf.expand(ikm, salt, info);

println!("sha1 okm: {}", bytes_to_hex(&okm));
assert_eq!(okm.len(), algo_output_length);
}

#[test]
fn test_hdkf_expand_with_salt() {
let salt = get_random_bytes(32);
let ikm = b"";
let info = b"";
let algo = Algorithm::SHA256;
let algo_output_length = algo.output_length();
let hkdf = HkdfWrapper::new(algo);
let okm = hkdf.expand(ikm, &salt, info);

println!("sha256 okm: {}", bytes_to_hex(&okm));
assert_eq!(okm.len(), algo_output_length);
}

#[test]
fn test_hdkf_expand_with_ikm() {
let salt = b"";
let ikm = b"kjhjason";
let info = b"";
let algo = Algorithm::SHA384;
let algo_output_length = algo.output_length();
let hkdf = HkdfWrapper::new(algo);
let okm = hkdf.expand(ikm.as_ref(), salt, info);

println!("sha384 okm: {}", bytes_to_hex(&okm));
assert_eq!(okm.len(), algo_output_length);
}

#[test]
fn test_hdkf_expand_with_info() {
let salt = b"";
let ikm = b"";
let info = b"kjhjason";
let algo = Algorithm::SHA512;
let algo_output_length = algo.output_length();
let hkdf = HkdfWrapper::new(algo);
let okm = hkdf.expand(ikm, salt, info);

println!("sha512 okm: {}", bytes_to_hex(&okm));
assert_eq!(okm.len(), algo_output_length);
}

#[test]
fn test_hdkf_expand_with_all() {
let salt = b"kjhjason.com";
let ikm = b"jason";
let info = b"kjhjason";
let algo = Algorithm::SHA256;
let algo_output_length = algo.output_length();
let hkdf = HkdfWrapper::new(algo);
let okm = hkdf.expand(ikm, salt, info);

println!("sha256 okm: {}", bytes_to_hex(&okm));
assert_eq!(okm.len(), algo_output_length);
#[cfg(feature = "ring")]
pub fn expand(&self, ikm: &[u8], salt: &[u8], info: &[u8]) -> Vec<u8> {
let hkdf_algo = self.algo.to_hkdf();
let prk = hkdf::Salt::new(hkdf_algo, salt).extract(ikm);

let mut okm = vec![0u8; self.algo.output_length()];
let okm_slice = &mut okm[..];
prk.expand(&[info], self.algo.to_hmac())
.expect("could not expand key due to possibly invalid length")
.fill(okm_slice)
.expect("could not fill key due to possibly invalid length");
okm
}
}
Loading

0 comments on commit 0baf5ef

Please sign in to comment.