Skip to main content

voided_core/hash/
mod.rs

1//! Hashing module providing SHA-256, SHA-512, HMAC, and PBKDF2.
2
3use crate::{Error, Result};
4use alloc::{string::String, vec::Vec};
5use hmac::{Hmac, Mac};
6use sha2::{Digest, Sha256, Sha512};
7
8/// Supported hash algorithms
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum HashAlgorithm {
11    /// SHA-256 (32 bytes output)
12    Sha256,
13    /// SHA-512 (64 bytes output)
14    Sha512,
15}
16
17impl HashAlgorithm {
18    /// Get output length in bytes
19    pub fn output_len(&self) -> usize {
20        match self {
21            HashAlgorithm::Sha256 => 32,
22            HashAlgorithm::Sha512 => 64,
23        }
24    }
25}
26
27/// Generate a hash using the specified algorithm
28pub fn hash(data: &[u8], algorithm: HashAlgorithm) -> Vec<u8> {
29    match algorithm {
30        HashAlgorithm::Sha256 => {
31            let mut hasher = Sha256::new();
32            hasher.update(data);
33            hasher.finalize().to_vec()
34        }
35        HashAlgorithm::Sha512 => {
36            let mut hasher = Sha512::new();
37            hasher.update(data);
38            hasher.finalize().to_vec()
39        }
40    }
41}
42
43/// Generate a hash and return as hex string
44pub fn hash_hex(data: &[u8], algorithm: HashAlgorithm) -> String {
45    hex::encode(hash(data, algorithm))
46}
47
48/// Generate a hash with salt
49pub fn hash_with_salt(data: &[u8], salt: &[u8], algorithm: HashAlgorithm) -> Vec<u8> {
50    let mut combined = Vec::with_capacity(data.len() + salt.len());
51    combined.extend_from_slice(data);
52    combined.extend_from_slice(salt);
53    hash(&combined, algorithm)
54}
55
56/// Generate a hash with salt and return as hex string
57pub fn hash_with_salt_hex(data: &[u8], salt: &[u8], algorithm: HashAlgorithm) -> String {
58    hex::encode(hash_with_salt(data, salt, algorithm))
59}
60
61/// Compare two hashes in constant time to prevent timing attacks
62pub fn compare_hashes(a: &[u8], b: &[u8]) -> bool {
63    constant_time_eq::constant_time_eq(a, b)
64}
65
66/// Generate HMAC using the specified algorithm
67pub fn generate_hmac(data: &[u8], key: &[u8], algorithm: HashAlgorithm) -> Result<Vec<u8>> {
68    match algorithm {
69        HashAlgorithm::Sha256 => {
70            let mut mac = Hmac::<Sha256>::new_from_slice(key)
71                .map_err(|e| Error::HashFailed(e.to_string()))?;
72            mac.update(data);
73            Ok(mac.finalize().into_bytes().to_vec())
74        }
75        HashAlgorithm::Sha512 => {
76            let mut mac = Hmac::<Sha512>::new_from_slice(key)
77                .map_err(|e| Error::HashFailed(e.to_string()))?;
78            mac.update(data);
79            Ok(mac.finalize().into_bytes().to_vec())
80        }
81    }
82}
83
84/// Generate HMAC and return as hex string
85pub fn generate_hmac_hex(data: &[u8], key: &[u8], algorithm: HashAlgorithm) -> Result<String> {
86    Ok(hex::encode(generate_hmac(data, key, algorithm)?))
87}
88
89/// Verify HMAC in constant time
90pub fn verify_hmac(
91    data: &[u8],
92    expected_mac: &[u8],
93    key: &[u8],
94    algorithm: HashAlgorithm,
95) -> Result<bool> {
96    let actual_mac = generate_hmac(data, key, algorithm)?;
97    Ok(compare_hashes(&actual_mac, expected_mac))
98}
99
100/// Hash data using PBKDF2-HMAC-SHA256 with high iterations
101pub fn hash_with_pbkdf2(data: &[u8], salt: &[u8], iterations: u32) -> Vec<u8> {
102    use pbkdf2::pbkdf2_hmac;
103
104    let mut output = [0u8; 32];
105    pbkdf2_hmac::<Sha256>(data, salt, iterations, &mut output);
106    output.to_vec()
107}
108
109/// Verify data against a PBKDF2 hash
110pub fn verify_pbkdf2(data: &[u8], expected_hash: &[u8], salt: &[u8], iterations: u32) -> bool {
111    let actual_hash = hash_with_pbkdf2(data, salt, iterations);
112    compare_hashes(&actual_hash, expected_hash)
113}
114
115/// Generate a fingerprint (truncated hash)
116/// Returns `length` bytes as hex (so 2*length hex characters)
117pub fn generate_fingerprint(data: &[u8], length: usize) -> String {
118    let hash = hash_hex(data, HashAlgorithm::Sha256);
119    // Each byte is 2 hex chars, so we take length*2 hex chars
120    let hex_len = (length * 2).min(hash.len());
121    hash[..hex_len].to_string()
122}
123
124/// Generate safety numbers (Signal-style) for key verification
125pub fn generate_safety_numbers(data: &[u8], group_size: usize) -> String {
126    let hash_bytes = hash(data, HashAlgorithm::Sha256);
127    format_safety_numbers(&hash_bytes, group_size)
128}
129
130fn format_safety_numbers(hash_bytes: &[u8], group_size: usize) -> String {
131    let mut groups = Vec::new();
132
133    for chunk in hash_bytes.chunks(group_size) {
134        let group: Vec<String> = chunk.iter().map(|&byte| format!("{:03}", byte)).collect();
135        groups.push(group.join(" "));
136    }
137
138    groups.join("  ")
139}
140
141/// Generate random bytes
142pub fn generate_random_bytes(length: usize) -> Vec<u8> {
143    use rand::RngCore;
144    let mut bytes = vec![0u8; length];
145    rand::thread_rng().fill_bytes(&mut bytes);
146    bytes
147}
148
149/// Generate a random salt
150pub fn generate_salt(length: usize) -> Vec<u8> {
151    generate_random_bytes(length)
152}
153
154/// Securely wipe a buffer
155pub fn secure_wipe(buffer: &mut [u8]) {
156    use zeroize::Zeroize;
157    buffer.zeroize();
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn test_sha256() {
166        let data = b"hello world";
167        let hash = hash_hex(data, HashAlgorithm::Sha256);
168        // Known SHA-256 hash of "hello world"
169        assert_eq!(
170            hash,
171            "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
172        );
173    }
174
175    #[test]
176    fn test_sha512() {
177        let data = b"hello world";
178        let hash = hash_hex(data, HashAlgorithm::Sha512);
179        assert_eq!(hash.len(), 128); // 64 bytes = 128 hex chars
180    }
181
182    #[test]
183    fn test_hash_with_salt() {
184        let data = b"password";
185        let salt = b"random_salt";
186
187        let hash1 = hash_with_salt_hex(data, salt, HashAlgorithm::Sha256);
188        let hash2 = hash_with_salt_hex(data, salt, HashAlgorithm::Sha256);
189
190        // Same inputs should produce same hash
191        assert_eq!(hash1, hash2);
192
193        // Different salt should produce different hash
194        let hash3 = hash_with_salt_hex(data, b"different_salt", HashAlgorithm::Sha256);
195        assert_ne!(hash1, hash3);
196    }
197
198    #[test]
199    fn test_hmac() {
200        let data = b"message";
201        let key = b"secret_key";
202
203        let mac = generate_hmac_hex(data, key, HashAlgorithm::Sha256).unwrap();
204        assert_eq!(mac.len(), 64); // 32 bytes = 64 hex chars
205
206        // Verify should pass with correct data
207        let mac_bytes = hex::decode(&mac).unwrap();
208        assert!(verify_hmac(data, &mac_bytes, key, HashAlgorithm::Sha256).unwrap());
209
210        // Verify should fail with wrong data
211        assert!(!verify_hmac(b"wrong", &mac_bytes, key, HashAlgorithm::Sha256).unwrap());
212    }
213
214    #[test]
215    fn test_pbkdf2() {
216        let password = b"my_password";
217        let salt = b"my_salt";
218        let iterations = 1000;
219
220        let hash1 = hash_with_pbkdf2(password, salt, iterations);
221        let hash2 = hash_with_pbkdf2(password, salt, iterations);
222
223        assert_eq!(hash1, hash2);
224        assert!(verify_pbkdf2(password, &hash1, salt, iterations));
225        assert!(!verify_pbkdf2(b"wrong_password", &hash1, salt, iterations));
226    }
227
228    #[test]
229    fn test_compare_hashes_constant_time() {
230        let hash1 = hash(b"test", HashAlgorithm::Sha256);
231        let hash2 = hash(b"test", HashAlgorithm::Sha256);
232        let hash3 = hash(b"different", HashAlgorithm::Sha256);
233
234        assert!(compare_hashes(&hash1, &hash2));
235        assert!(!compare_hashes(&hash1, &hash3));
236    }
237
238    #[test]
239    fn test_fingerprint() {
240        let data = b"some key material";
241        // Request 8 bytes, get 16 hex characters (2 hex chars per byte)
242        let fp = generate_fingerprint(data, 8);
243        assert_eq!(fp.len(), 16);
244
245        // Request 4 bytes, get 8 hex characters
246        let fp2 = generate_fingerprint(data, 4);
247        assert_eq!(fp2.len(), 8);
248    }
249
250    #[test]
251    fn test_safety_numbers() {
252        let data = b"public key data";
253        let numbers = generate_safety_numbers(data, 5);
254        assert!(!numbers.is_empty());
255        // Should contain groups of 3-digit numbers
256        assert!(numbers.contains(' '));
257    }
258}