use aes_gcm::{Aes256Gcm, Nonce, KeyInit};
use aes_gcm::aead::Aead;
use sha2::{Sha256, Digest};
use hmac::{Hmac, Mac};
use rand::Rng;
type HmacSha256 = Hmac<Sha256>;
pub struct Crypto;
impl Crypto {
pub fn sha256(data: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(data.as_bytes());
hex::encode(hasher.finalize())
}
pub fn hmac_sha256(key: &[u8], data: &str) -> String {
let mut mac = <HmacSha256 as hmac::digest::KeyInit>::new_from_slice(key)
.expect("HMAC key size error");
mac.update(data.as_bytes());
hex::encode(mac.finalize().into_bytes())
}
pub fn aes_encrypt(key: &[u8], plaintext: &str) -> Option<(String, String)> {
if key.len() != 32 { return None; }
let cipher = Aes256Gcm::new_from_slice(key).ok()?;
let mut nonce_bytes = [0u8; 12];
rand::thread_rng().fill(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let ciphertext = cipher.encrypt(nonce, plaintext.as_bytes()).ok()?;
Some((
base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &ciphertext),
base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &nonce_bytes),
))
}
pub fn aes_decrypt(key: &[u8], ciphertext_b64: &str, nonce_b64: &str) -> Option<String> {
if key.len() != 32 { return None; }
let cipher = Aes256Gcm::new_from_slice(key).ok()?;
let ciphertext = base64::Engine::decode(
&base64::engine::general_purpose::STANDARD, ciphertext_b64
).ok()?;
let nonce_vec = base64::Engine::decode(
&base64::engine::general_purpose::STANDARD, nonce_b64
).ok()?;
let nonce = Nonce::from_slice(&nonce_vec);
let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()).ok()?;
String::from_utf8(plaintext).ok()
}
pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
use argon2::{Argon2, PasswordHasher};
let salt = argon2::password_hash::SaltString::generate(&mut rand::thread_rng());
let argon2 = Argon2::default();
let hash = argon2.hash_password(password.as_bytes(), &salt)?;
Ok(hash.to_string())
}
pub fn verify_password(password: &str, hash: &str) -> Result<bool, argon2::password_hash::Error> {
use argon2::{Argon2, PasswordVerifier, PasswordHash};
let parsed = PasswordHash::new(hash)?;
let argon2 = Argon2::default();
Ok(argon2.verify_password(password.as_bytes(), &parsed).is_ok())
}
pub fn base64_url_encode(data: &[u8]) -> String {
base64::Engine::encode(&base64::engine::general_purpose::URL_SAFE_NO_PAD, data)
}
pub fn base64_url_decode(s: &str) -> Option<Vec<u8>> {
base64::Engine::decode(&base64::engine::general_purpose::URL_SAFE_NO_PAD, s).ok()
}
pub fn random_key() -> Vec<u8> {
let mut key = vec![0u8; 32];
rand::thread_rng().fill(&mut key[..]);
key
}
pub fn random_token(len: usize) -> String {
let mut bytes = vec![0u8; len];
rand::thread_rng().fill(&mut bytes[..]);
hex::encode(bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sha256() { assert_eq!(Crypto::sha256("hello").len(), 64); }
#[test]
fn test_encrypt_decrypt() {
let key = vec![0u8; 32];
let (ct, nonce64) = Crypto::aes_encrypt(&key, "test-data").unwrap();
let pt = Crypto::aes_decrypt(&key, &ct, &nonce64).unwrap();
assert_eq!(pt, "test-data");
}
#[test]
fn test_password() {
let hash = Crypto::hash_password("Alun@2024").unwrap();
assert!(Crypto::verify_password("Alun@2024", &hash).unwrap());
}
#[test]
fn test_random_key() { assert_eq!(Crypto::random_key().len(), 32); }
}