Skip to content

Commit e4ab352

Browse files
committed
v2.0.0: Enhanced with key exchange, hashing, and key wrapping
- Added X25519 Diffie-Hellman key exchange - Added multiple hash algorithms (SHA-256, SHA-3, BLAKE3) - Added AES key wrapping for key hierarchies - New modules: keyexchange.rs, hashing.rs, keywrap.rs
1 parent 9e7602c commit e4ab352

File tree

5 files changed

+905
-5
lines changed

5 files changed

+905
-5
lines changed

Cargo.toml

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,46 @@
11
[package]
22
name = "rust-crypto-utils"
3-
version = "0.1.0"
3+
version = "2.0.0"
44
edition = "2021"
55
authors = ["Tony Chuks Awunor <[email protected]>"]
6-
description = "Memory-safe cryptographic utilities for secure key handling and encryption"
6+
description = "Memory-safe cryptographic utilities for secure key handling, encryption, and post-quantum readiness"
77
license = "MIT"
88
repository = "https://github.com/guardsarm/rust-crypto-utils"
99
keywords = ["cryptography", "security", "encryption", "memory-safe", "financial"]
1010
categories = ["cryptography", "authentication"]
11+
readme = "README.md"
12+
13+
[features]
14+
default = ["std"]
15+
std = []
16+
async = ["tokio"]
17+
hardware-acceleration = []
1118

1219
[dependencies]
1320
argon2 = "0.5"
1421
aes-gcm = "0.10"
22+
chacha20poly1305 = "0.10"
1523
rand = "0.8"
1624
zeroize = { version = "1.7", features = ["derive"] }
1725
hex = "0.4"
1826
thiserror = "1.0"
1927
hmac = "0.12"
2028
sha2 = "0.10"
29+
sha3 = "0.10"
30+
blake3 = "1.5"
2131
pbkdf2 = "0.12"
2232
hkdf = "0.12"
2333
ed25519-dalek = "2.1"
34+
x25519-dalek = { version = "2.0", features = ["static_secrets"] }
2435
serde = { version = "1.0", features = ["derive"] }
2536
serde_json = "1.0"
37+
base64 = "0.21"
38+
tokio = { version = "1.35", features = ["full"], optional = true }
39+
constant_time_eq = "0.3"
2640

2741
[dev-dependencies]
2842
criterion = "0.5"
43+
tempfile = "3.8"
2944

3045
[[example]]
3146
name = "password_hashing"
@@ -34,3 +49,11 @@ path = "examples/password_hashing.rs"
3449
[[example]]
3550
name = "file_encryption"
3651
path = "examples/file_encryption.rs"
52+
53+
[[example]]
54+
name = "key_exchange"
55+
path = "examples/key_exchange.rs"
56+
57+
[[bench]]
58+
name = "crypto_benchmark"
59+
harness = false

src/hashing.rs

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
//! Cryptographic hashing module v2.0
2+
//!
3+
//! Provides multiple hash algorithms: SHA-256, SHA-3, and BLAKE3.
4+
5+
use serde::{Deserialize, Serialize};
6+
use sha2::{Sha256, Sha512, Digest as Sha2Digest};
7+
use sha3::{Sha3_256, Sha3_512};
8+
use thiserror::Error;
9+
10+
/// Hashing errors
11+
#[derive(Error, Debug)]
12+
pub enum HashError {
13+
#[error("Invalid hash length")]
14+
InvalidLength,
15+
16+
#[error("Unsupported algorithm")]
17+
UnsupportedAlgorithm,
18+
}
19+
20+
/// Supported hash algorithms
21+
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
22+
pub enum HashAlgorithm {
23+
Sha256,
24+
Sha512,
25+
Sha3_256,
26+
Sha3_512,
27+
Blake3,
28+
}
29+
30+
impl HashAlgorithm {
31+
/// Get output length in bytes
32+
pub fn output_length(&self) -> usize {
33+
match self {
34+
HashAlgorithm::Sha256 => 32,
35+
HashAlgorithm::Sha512 => 64,
36+
HashAlgorithm::Sha3_256 => 32,
37+
HashAlgorithm::Sha3_512 => 64,
38+
HashAlgorithm::Blake3 => 32,
39+
}
40+
}
41+
42+
/// Get algorithm name
43+
pub fn name(&self) -> &'static str {
44+
match self {
45+
HashAlgorithm::Sha256 => "SHA-256",
46+
HashAlgorithm::Sha512 => "SHA-512",
47+
HashAlgorithm::Sha3_256 => "SHA3-256",
48+
HashAlgorithm::Sha3_512 => "SHA3-512",
49+
HashAlgorithm::Blake3 => "BLAKE3",
50+
}
51+
}
52+
}
53+
54+
/// Hash output wrapper
55+
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
56+
pub struct HashOutput {
57+
algorithm: HashAlgorithm,
58+
bytes: Vec<u8>,
59+
}
60+
61+
impl HashOutput {
62+
/// Create from raw bytes
63+
pub fn from_bytes(algorithm: HashAlgorithm, bytes: Vec<u8>) -> Result<Self, HashError> {
64+
if bytes.len() != algorithm.output_length() {
65+
return Err(HashError::InvalidLength);
66+
}
67+
Ok(Self { algorithm, bytes })
68+
}
69+
70+
/// Get raw bytes
71+
pub fn as_bytes(&self) -> &[u8] {
72+
&self.bytes
73+
}
74+
75+
/// Convert to hex string
76+
pub fn to_hex(&self) -> String {
77+
hex::encode(&self.bytes)
78+
}
79+
80+
/// Get algorithm used
81+
pub fn algorithm(&self) -> HashAlgorithm {
82+
self.algorithm
83+
}
84+
85+
/// Verify against expected hash (constant-time)
86+
pub fn verify(&self, expected: &[u8]) -> bool {
87+
constant_time_eq::constant_time_eq(&self.bytes, expected)
88+
}
89+
90+
/// Verify against hex string (constant-time)
91+
pub fn verify_hex(&self, expected_hex: &str) -> bool {
92+
if let Ok(expected) = hex::decode(expected_hex) {
93+
self.verify(&expected)
94+
} else {
95+
false
96+
}
97+
}
98+
}
99+
100+
impl std::fmt::Debug for HashOutput {
101+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102+
write!(f, "HashOutput({}: {})", self.algorithm.name(), &self.to_hex()[..16])
103+
}
104+
}
105+
106+
/// Universal hasher supporting multiple algorithms
107+
pub struct Hasher {
108+
algorithm: HashAlgorithm,
109+
}
110+
111+
impl Hasher {
112+
/// Create a new hasher with the specified algorithm
113+
pub fn new(algorithm: HashAlgorithm) -> Self {
114+
Self { algorithm }
115+
}
116+
117+
/// Hash data and return output
118+
pub fn hash(&self, data: &[u8]) -> HashOutput {
119+
let bytes = match self.algorithm {
120+
HashAlgorithm::Sha256 => {
121+
let mut hasher = Sha256::new();
122+
hasher.update(data);
123+
hasher.finalize().to_vec()
124+
}
125+
HashAlgorithm::Sha512 => {
126+
let mut hasher = Sha512::new();
127+
hasher.update(data);
128+
hasher.finalize().to_vec()
129+
}
130+
HashAlgorithm::Sha3_256 => {
131+
let mut hasher = Sha3_256::new();
132+
hasher.update(data);
133+
hasher.finalize().to_vec()
134+
}
135+
HashAlgorithm::Sha3_512 => {
136+
let mut hasher = Sha3_512::new();
137+
hasher.update(data);
138+
hasher.finalize().to_vec()
139+
}
140+
HashAlgorithm::Blake3 => {
141+
let hash = blake3::hash(data);
142+
hash.as_bytes().to_vec()
143+
}
144+
};
145+
146+
HashOutput {
147+
algorithm: self.algorithm,
148+
bytes,
149+
}
150+
}
151+
152+
/// Hash string data
153+
pub fn hash_str(&self, data: &str) -> HashOutput {
154+
self.hash(data.as_bytes())
155+
}
156+
157+
/// Hash and return hex string
158+
pub fn hash_to_hex(&self, data: &[u8]) -> String {
159+
self.hash(data).to_hex()
160+
}
161+
}
162+
163+
impl Default for Hasher {
164+
fn default() -> Self {
165+
Self::new(HashAlgorithm::Sha256)
166+
}
167+
}
168+
169+
/// Incremental hasher for streaming data
170+
pub struct IncrementalHasher {
171+
state: IncrementalState,
172+
algorithm: HashAlgorithm,
173+
}
174+
175+
enum IncrementalState {
176+
Sha256(Sha256),
177+
Sha512(Sha512),
178+
Sha3_256(Sha3_256),
179+
Sha3_512(Sha3_512),
180+
Blake3(blake3::Hasher),
181+
}
182+
183+
impl IncrementalHasher {
184+
/// Create a new incremental hasher
185+
pub fn new(algorithm: HashAlgorithm) -> Self {
186+
let state = match algorithm {
187+
HashAlgorithm::Sha256 => IncrementalState::Sha256(Sha256::new()),
188+
HashAlgorithm::Sha512 => IncrementalState::Sha512(Sha512::new()),
189+
HashAlgorithm::Sha3_256 => IncrementalState::Sha3_256(Sha3_256::new()),
190+
HashAlgorithm::Sha3_512 => IncrementalState::Sha3_512(Sha3_512::new()),
191+
HashAlgorithm::Blake3 => IncrementalState::Blake3(blake3::Hasher::new()),
192+
};
193+
Self { state, algorithm }
194+
}
195+
196+
/// Update with more data
197+
pub fn update(&mut self, data: &[u8]) {
198+
match &mut self.state {
199+
IncrementalState::Sha256(h) => h.update(data),
200+
IncrementalState::Sha512(h) => h.update(data),
201+
IncrementalState::Sha3_256(h) => h.update(data),
202+
IncrementalState::Sha3_512(h) => h.update(data),
203+
IncrementalState::Blake3(h) => { h.update(data); }
204+
}
205+
}
206+
207+
/// Finalize and return hash output
208+
pub fn finalize(self) -> HashOutput {
209+
let bytes = match self.state {
210+
IncrementalState::Sha256(h) => h.finalize().to_vec(),
211+
IncrementalState::Sha512(h) => h.finalize().to_vec(),
212+
IncrementalState::Sha3_256(h) => h.finalize().to_vec(),
213+
IncrementalState::Sha3_512(h) => h.finalize().to_vec(),
214+
IncrementalState::Blake3(h) => h.finalize().as_bytes().to_vec(),
215+
};
216+
217+
HashOutput {
218+
algorithm: self.algorithm,
219+
bytes,
220+
}
221+
}
222+
}
223+
224+
/// Convenience functions
225+
pub fn sha256(data: &[u8]) -> HashOutput {
226+
Hasher::new(HashAlgorithm::Sha256).hash(data)
227+
}
228+
229+
pub fn sha3_256(data: &[u8]) -> HashOutput {
230+
Hasher::new(HashAlgorithm::Sha3_256).hash(data)
231+
}
232+
233+
pub fn blake3(data: &[u8]) -> HashOutput {
234+
Hasher::new(HashAlgorithm::Blake3).hash(data)
235+
}
236+
237+
#[cfg(test)]
238+
mod tests {
239+
use super::*;
240+
241+
#[test]
242+
fn test_sha256() {
243+
let hasher = Hasher::new(HashAlgorithm::Sha256);
244+
let hash = hasher.hash(b"hello world");
245+
assert_eq!(hash.as_bytes().len(), 32);
246+
assert_eq!(hash.algorithm(), HashAlgorithm::Sha256);
247+
}
248+
249+
#[test]
250+
fn test_sha3_256() {
251+
let hasher = Hasher::new(HashAlgorithm::Sha3_256);
252+
let hash = hasher.hash(b"hello world");
253+
assert_eq!(hash.as_bytes().len(), 32);
254+
assert_eq!(hash.algorithm(), HashAlgorithm::Sha3_256);
255+
}
256+
257+
#[test]
258+
fn test_blake3() {
259+
let hasher = Hasher::new(HashAlgorithm::Blake3);
260+
let hash = hasher.hash(b"hello world");
261+
assert_eq!(hash.as_bytes().len(), 32);
262+
assert_eq!(hash.algorithm(), HashAlgorithm::Blake3);
263+
}
264+
265+
#[test]
266+
fn test_different_algorithms_different_output() {
267+
let data = b"test data";
268+
let sha256 = Hasher::new(HashAlgorithm::Sha256).hash(data);
269+
let sha3 = Hasher::new(HashAlgorithm::Sha3_256).hash(data);
270+
let blake = Hasher::new(HashAlgorithm::Blake3).hash(data);
271+
272+
assert_ne!(sha256.as_bytes(), sha3.as_bytes());
273+
assert_ne!(sha256.as_bytes(), blake.as_bytes());
274+
assert_ne!(sha3.as_bytes(), blake.as_bytes());
275+
}
276+
277+
#[test]
278+
fn test_verify() {
279+
let hasher = Hasher::new(HashAlgorithm::Sha256);
280+
let hash = hasher.hash(b"password");
281+
282+
assert!(hash.verify(hash.as_bytes()));
283+
assert!(!hash.verify(b"wrong hash"));
284+
}
285+
286+
#[test]
287+
fn test_verify_hex() {
288+
let hasher = Hasher::new(HashAlgorithm::Sha256);
289+
let hash = hasher.hash(b"test");
290+
let hex = hash.to_hex();
291+
292+
assert!(hash.verify_hex(&hex));
293+
assert!(!hash.verify_hex("0000"));
294+
}
295+
296+
#[test]
297+
fn test_incremental_hasher() {
298+
let data = b"hello world";
299+
300+
// One-shot hash
301+
let one_shot = Hasher::new(HashAlgorithm::Sha256).hash(data);
302+
303+
// Incremental hash
304+
let mut incremental = IncrementalHasher::new(HashAlgorithm::Sha256);
305+
incremental.update(b"hello ");
306+
incremental.update(b"world");
307+
let result = incremental.finalize();
308+
309+
assert_eq!(one_shot.as_bytes(), result.as_bytes());
310+
}
311+
312+
#[test]
313+
fn test_convenience_functions() {
314+
let data = b"test";
315+
316+
let h1 = sha256(data);
317+
let h2 = sha3_256(data);
318+
let h3 = blake3(data);
319+
320+
assert_eq!(h1.algorithm(), HashAlgorithm::Sha256);
321+
assert_eq!(h2.algorithm(), HashAlgorithm::Sha3_256);
322+
assert_eq!(h3.algorithm(), HashAlgorithm::Blake3);
323+
}
324+
325+
#[test]
326+
fn test_hex_conversion() {
327+
let hash = sha256(b"test");
328+
let hex = hash.to_hex();
329+
assert_eq!(hex.len(), 64); // 32 bytes = 64 hex chars
330+
}
331+
}

0 commit comments

Comments
 (0)