1use aes_gcm::{Aes256Gcm, Nonce, KeyInit};
4use aes_gcm::aead::Aead;
5use sha2::{Sha256, Digest};
6use hmac::{Hmac, Mac};
7use rand::Rng;
8
9type HmacSha256 = Hmac<Sha256>;
10
11pub struct Crypto;
16
17impl Crypto {
18 pub fn sha256(data: &str) -> String {
20 let mut hasher = Sha256::new();
21 hasher.update(data.as_bytes());
22 hex::encode(hasher.finalize())
23 }
24
25 pub fn hmac_sha256(key: &[u8], data: &str) -> String {
27 let mut mac = <HmacSha256 as hmac::digest::KeyInit>::new_from_slice(key)
28 .expect("HMAC key size error");
29 mac.update(data.as_bytes());
30 hex::encode(mac.finalize().into_bytes())
31 }
32
33 pub fn aes_encrypt(key: &[u8], plaintext: &str) -> Option<(String, String)> {
38 if key.len() != 32 { return None; }
39 let cipher = Aes256Gcm::new_from_slice(key).ok()?;
40 let mut nonce_bytes = [0u8; 12];
41 rand::thread_rng().fill(&mut nonce_bytes);
42 let nonce = Nonce::from_slice(&nonce_bytes);
43 let ciphertext = cipher.encrypt(nonce, plaintext.as_bytes()).ok()?;
44 Some((
45 base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &ciphertext),
46 base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &nonce_bytes),
47 ))
48 }
49
50 pub fn aes_decrypt(key: &[u8], ciphertext_b64: &str, nonce_b64: &str) -> Option<String> {
52 if key.len() != 32 { return None; }
53 let cipher = Aes256Gcm::new_from_slice(key).ok()?;
54 let ciphertext = base64::Engine::decode(
55 &base64::engine::general_purpose::STANDARD, ciphertext_b64
56 ).ok()?;
57 let nonce_vec = base64::Engine::decode(
58 &base64::engine::general_purpose::STANDARD, nonce_b64
59 ).ok()?;
60 let nonce = Nonce::from_slice(&nonce_vec);
61 let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()).ok()?;
62 String::from_utf8(plaintext).ok()
63 }
64
65 pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
67 use argon2::{Argon2, PasswordHasher};
68 let salt = argon2::password_hash::SaltString::generate(&mut rand::thread_rng());
69 let argon2 = Argon2::default();
70 let hash = argon2.hash_password(password.as_bytes(), &salt)?;
71 Ok(hash.to_string())
72 }
73
74 pub fn verify_password(password: &str, hash: &str) -> Result<bool, String> {
78 if hash.starts_with("$argon2") {
79 use argon2::{Argon2, PasswordVerifier, PasswordHash};
80 let parsed = PasswordHash::new(hash)
81 .map_err(|e| format!("Argon2 hash parse error: {e}"))?;
82 let argon2 = Argon2::default();
83 Ok(argon2.verify_password(password.as_bytes(), &parsed).is_ok())
84 } else if hash.starts_with("$2") {
85 bcrypt::verify(password, hash)
86 .map_err(|e| format!("BCrypt verify error: {e}"))
87 } else {
88 Err("Unknown hash format".into())
89 }
90 }
91
92 pub fn base64_url_encode(data: &[u8]) -> String {
94 base64::Engine::encode(&base64::engine::general_purpose::URL_SAFE_NO_PAD, data)
95 }
96
97 pub fn base64_url_decode(s: &str) -> Option<Vec<u8>> {
99 base64::Engine::decode(&base64::engine::general_purpose::URL_SAFE_NO_PAD, s).ok()
100 }
101
102 pub fn random_key() -> Vec<u8> {
104 let mut key = vec![0u8; 32];
105 rand::thread_rng().fill(&mut key[..]);
106 key
107 }
108
109 pub fn random_token(len: usize) -> String {
111 let mut bytes = vec![0u8; len];
112 rand::thread_rng().fill(&mut bytes[..]);
113 hex::encode(bytes)
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 #[test]
121 fn test_sha256() { assert_eq!(Crypto::sha256("hello").len(), 64); }
122 #[test]
123 fn test_encrypt_decrypt() {
124 let key = vec![0u8; 32];
125 let (ct, nonce64) = Crypto::aes_encrypt(&key, "test-data").unwrap();
126 let pt = Crypto::aes_decrypt(&key, &ct, &nonce64).unwrap();
127 assert_eq!(pt, "test-data");
128 }
129 #[test]
130 fn test_password() {
131 let hash = Crypto::hash_password("Alun@2024").unwrap();
132 assert!(Crypto::verify_password("Alun@2024", &hash).unwrap());
133 }
134 #[test]
135 fn test_random_key() { assert_eq!(Crypto::random_key().len(), 32); }
136}