use argon2::{
Argon2,
password_hash::{
PasswordHash, PasswordHasher, PasswordVerifier, SaltString,
rand_core::OsRng,
},
};
use rand::RngExt;
use sha2::{Digest, Sha256};
pub type CryptoResult<T> = Result<T, CryptoError>;
#[derive(Debug, thiserror::Error)]
pub enum CryptoError {
#[error("Password hashing failed: {0}")]
HashError(String),
#[error("Password verification failed: {0}")]
VerifyError(String),
#[error("Invalid password hash format")]
InvalidHashFormat,
}
pub fn hash_password(password: &str) -> CryptoResult<String> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
argon2
.hash_password(password.as_bytes(), &salt)
.map(|hash| hash.to_string())
.map_err(|e| CryptoError::HashError(e.to_string()))
}
pub fn verify_password(password: &str, hash: &str) -> CryptoResult<bool> {
let parsed_hash =
PasswordHash::new(hash).map_err(|_| CryptoError::InvalidHashFormat)?;
let argon2 = Argon2::default();
match argon2.verify_password(password.as_bytes(), &parsed_hash) {
Ok(_) => Ok(true),
Err(argon2::password_hash::Error::Password) => Ok(false),
Err(e) => Err(CryptoError::VerifyError(e.to_string())),
}
}
pub fn generate_api_key(prefix: &str) -> String {
let mut rng = rand::rng();
let random_bytes: [u8; 20] = rng.random();
let random_hex = hex::encode(random_bytes);
format!("{prefix}{random_hex}")
}
pub fn extract_key_prefix(api_key: &str, prefix: &str) -> String {
api_key[..prefix.len()].to_string()
}
pub fn hash_api_key(api_key: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(api_key.as_bytes());
let result = hasher.finalize();
hex::encode(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_password_hashing() {
let password = "MySecurePassword123!";
let hash = hash_password(password).unwrap();
assert!(verify_password(password, &hash).unwrap());
assert!(!verify_password("WrongPassword", &hash).unwrap());
}
#[test]
fn test_password_hash_format() {
let password = "test";
let hash = hash_password(password).unwrap();
assert!(hash.starts_with("$argon2"));
}
#[test]
fn test_invalid_hash_format() {
let result = verify_password("test", "invalid_hash");
assert!(result.is_err());
}
#[test]
fn test_api_key_generation() {
let key = generate_api_key("ave_node_");
assert!(key.starts_with("ave_node_"));
assert_eq!(key.len(), 49);
}
#[test]
fn test_api_key_uniqueness() {
let key1 = generate_api_key("ave_node_");
let key2 = generate_api_key("ave_node_");
assert_ne!(key1, key2);
}
#[test]
fn test_extract_key_prefix() {
let key = "ave_node_abcdef1234567890";
let prefix = extract_key_prefix(key, "ave_node_");
assert_eq!(prefix, "ave_node_"); }
#[test]
fn test_api_key_hashing() {
let key = generate_api_key("ave_node_");
let hash = hash_api_key(&key);
assert_eq!(hash.len(), 64);
assert_eq!(hash_api_key(&key), hash);
assert_ne!(hash_api_key("wrong_key"), hash);
}
#[test]
fn test_api_key_hash_deterministic() {
let key = "ave_node_test123";
let hash1 = hash_api_key(&key);
let hash2 = hash_api_key(&key);
assert_eq!(hash1, hash2);
}
}