use super::hash_algorithm::HashAlgorithm;
use crate::algorithms;
use crate::models::hash_algorithm::HashingAlgorithm;
use algorithms::{argon2i::Argon2i, bcrypt::Bcrypt, scrypt::Scrypt};
use serde::{Deserialize, Serialize};
use argon2rs::argon2i_simple;
use base64::{engine::general_purpose, Engine as _};
use scrypt::scrypt;
use std::{fmt, str::FromStr};
use vrd::random::Random;
pub type Salt = Vec<u8>;
#[non_exhaustive]
#[derive(
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
)]
pub struct Hash {
pub hash: Vec<u8>,
pub salt: Salt,
pub algorithm: HashAlgorithm,
}
impl Hash {
pub fn new_argon2i(
password: &str,
salt: Salt,
) -> Result<Self, String> {
let salt_str = std::str::from_utf8(&salt)
.map_err(|_| "Failed to convert salt to string")?;
let calculated_hash =
argon2i_simple(password, salt_str).to_vec();
HashBuilder::new()
.hash(calculated_hash)
.salt(salt)
.algorithm(HashAlgorithm::Argon2i)
.build()
}
pub fn new_bcrypt(
password: &str,
cost: u32,
) -> Result<Self, String> {
let hashed_password =
bcrypt::hash(password, cost).map_err(|e| {
format!("Failed to hash password with Bcrypt: {}", e)
})?;
let empty_salt = Vec::new();
HashBuilder::new()
.hash(hashed_password.as_bytes().to_vec())
.salt(empty_salt)
.algorithm(HashAlgorithm::Bcrypt)
.build()
}
pub fn new_scrypt(
password: &str,
salt: Salt,
) -> Result<Self, String> {
let salt_str = std::str::from_utf8(&salt)
.map_err(|_| "Failed to convert salt to string")?;
let calculated_hash =
Scrypt::hash_password(password, salt_str)?;
HashBuilder::new()
.hash(calculated_hash)
.salt(salt)
.algorithm(HashAlgorithm::Scrypt)
.build()
}
pub fn algorithm(&self) -> HashAlgorithm {
self.algorithm
}
pub fn from_hash(hash: &[u8], algo: &str) -> Result<Self, String> {
let algorithm = match algo {
"argon2i" => Ok(HashAlgorithm::Argon2i),
"bcrypt" => Ok(HashAlgorithm::Bcrypt),
"scrypt" => Ok(HashAlgorithm::Scrypt),
_ => Err(format!("Unsupported hash algorithm: {}", algo)),
}?;
Ok(Hash {
salt: Vec::new(),
hash: hash.to_vec(),
algorithm,
})
}
pub fn from_string(hash_str: &str) -> Result<Self, String> {
let parts: Vec<&str> = hash_str.split('$').collect();
if parts.len() != 6 {
return Err(String::from("Invalid hash string"));
}
let algorithm = Self::parse_algorithm(hash_str)?;
let salt = format!(
"${}${}${}${}",
parts[1], parts[2], parts[3], parts[4]
);
let hash_bytes =
general_purpose::STANDARD.decode(parts[5]).map_err(
|_| format!("Failed to decode base64: {}", parts[5]),
)?;
Ok(Hash {
salt: salt.into_bytes(),
hash: hash_bytes,
algorithm,
})
}
pub fn generate_hash(
password: &str,
salt: &str,
algo: &str,
) -> Result<Vec<u8>, String> {
match algo {
"argon2i" => Argon2i::hash_password(password, salt),
"bcrypt" => Bcrypt::hash_password(password, salt),
"scrypt" => Scrypt::hash_password(password, salt),
_ => Err(format!("Unsupported hash algorithm: {}", algo)),
}
}
pub fn generate_random_string(len: usize) -> String {
let mut rng = Random::default();
let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
(0..len)
.map(|_| {
chars
.chars()
.nth(rng.random_range(0, chars.len() as u32)
as usize)
.unwrap()
})
.collect()
}
pub fn generate_salt(algo: &str) -> Result<String, String> {
let mut rng = Random::default();
match algo {
"argon2i" => Ok(Self::generate_random_string(16)),
"bcrypt" => {
let salt: Vec<u8> = rng.bytes(16);
let salt_array: [u8; 16] =
salt.try_into().map_err(|_| {
"Error: failed to convert salt to an array"
})?;
Ok(general_purpose::STANDARD.encode(&salt_array[..]))
}
"scrypt" => {
let salt: Vec<u8> = rng.bytes(32);
let salt_array: [u8; 32] =
salt.try_into().map_err(|_| {
"Error: failed to convert salt to an array"
})?;
Ok(general_purpose::STANDARD.encode(&salt_array[..]))
}
_ => Err(format!("Unsupported hash algorithm: {}", algo)),
}
}
pub fn hash(&self) -> &[u8] {
&self.hash
}
pub fn hash_length(&self) -> usize {
self.hash.len()
}
pub fn new(
password: &str,
salt: &str,
algo: &str,
) -> Result<Self, String> {
if password.len() < 8 {
return Err(String::from("Password is too short. It must be at least 8 characters."));
}
let hash = Self::generate_hash(password, salt, algo)?;
let algorithm = match algo {
"argon2i" => Ok(HashAlgorithm::Argon2i),
"bcrypt" => Ok(HashAlgorithm::Bcrypt),
"scrypt" => Ok(HashAlgorithm::Scrypt),
_ => Err(format!("Unsupported hash algorithm: {}", algo)),
}?;
Ok(Self {
hash,
salt: salt.as_bytes().to_vec(),
algorithm,
})
}
pub fn parse(
input: &str,
) -> Result<Self, Box<dyn std::error::Error>> {
let hash: Hash = serde_json::from_str(input)?;
Ok(hash)
}
pub fn parse_algorithm(
hash_str: &str,
) -> Result<HashAlgorithm, String> {
let parts: Vec<&str> = hash_str.split('$').collect();
if parts.len() < 2 {
return Err(String::from("Invalid hash string"));
}
match parts[1] {
"argon2i" => Ok(HashAlgorithm::Argon2i),
"bcrypt" => Ok(HashAlgorithm::Bcrypt),
"scrypt" => Ok(HashAlgorithm::Scrypt),
_ => {
Err(format!("Unsupported hash algorithm: {}", parts[1]))
}
}
}
pub fn salt(&self) -> &[u8] {
&self.salt
}
pub fn set_hash(&mut self, hash: &[u8]) {
self.hash = hash.to_vec();
}
pub fn set_password(
&mut self,
password: &str,
salt: &str,
algo: &str,
) -> Result<(), String> {
self.hash = Self::generate_hash(password, salt, algo)?;
Ok(())
}
pub fn set_salt(&mut self, salt: &[u8]) {
self.salt = salt.to_vec();
}
pub fn to_string_representation(&self) -> String {
let hash_str = self
.hash
.iter()
.map(|b| format!("{:02x}", b))
.collect::<Vec<String>>()
.join("");
format!("{}:{}", String::from_utf8_lossy(&self.salt), hash_str)
}
pub fn verify(&self, password: &str) -> Result<bool, &'static str> {
let salt = std::str::from_utf8(&self.salt)
.map_err(|_| "Failed to convert salt to string")?;
match self.algorithm {
HashAlgorithm::Argon2i => {
let calculated_hash =
argon2i_simple(password, salt).to_vec();
println!("Algorithm: Argon2i");
println!(
"Provided password for verification: {}",
password
);
println!("Salt used for verification: {}", salt);
println!("Calculated Hash: {:?}", calculated_hash);
println!("Stored Hash: {:?}", self.hash);
Ok(calculated_hash == self.hash)
}
HashAlgorithm::Bcrypt => {
println!("Algorithm: Bcrypt");
println!(
"Provided password for verification: {}",
password
);
let hash_str = std::str::from_utf8(&self.hash)
.map_err(|_| "Failed to convert hash to string")?;
bcrypt::verify(password, hash_str)
.map_err(|_| "Failed to verify Bcrypt password")
}
HashAlgorithm::Scrypt => {
println!("Algorithm: Scrypt");
println!(
"Provided password for verification: {}",
password
);
println!("Salt used for verification: {}", salt);
let scrypt_params = scrypt::Params::new(14, 8, 1, 64)
.map_err(|_| {
"Failed to create Scrypt params"
})?;
let mut output = [0u8; 64];
match scrypt(
password.as_bytes(),
salt.as_bytes(),
&scrypt_params,
&mut output,
) {
Ok(_) => {
println!(
"Calculated Hash: {:?}",
output.to_vec()
);
println!("Stored Hash: {:?}", self.hash);
Ok(output.to_vec() == self.hash)
}
Err(_) => Err("Scrypt hashing failed"),
}
}
}
}
}
impl fmt::Display for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Hash {{ hash: {:?} }}", self.hash)
}
}
impl fmt::Display for HashAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl FromStr for HashAlgorithm {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let algorithm = match s {
"argon2i" => HashAlgorithm::Argon2i,
"bcrypt" => HashAlgorithm::Bcrypt,
"scrypt" => HashAlgorithm::Scrypt,
_ => return Err(String::from("Invalid hash algorithm")),
};
Ok(algorithm)
}
}
#[derive(
Clone,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
Serialize,
Deserialize,
)]
pub struct HashBuilder {
hash: Option<Vec<u8>>,
salt: Option<Salt>,
algorithm: Option<HashAlgorithm>,
}
impl HashBuilder {
pub fn new() -> Self {
HashBuilder {
hash: None,
salt: None,
algorithm: None,
}
}
pub fn hash(mut self, hash: Vec<u8>) -> Self {
self.hash = Some(hash);
self
}
pub fn salt(mut self, salt: Salt) -> Self {
self.salt = Some(salt);
self
}
pub fn algorithm(mut self, algorithm: HashAlgorithm) -> Self {
self.algorithm = Some(algorithm);
self
}
pub fn build(self) -> Result<Hash, String> {
if let (Some(hash), Some(salt), Some(algorithm)) =
(self.hash, self.salt, self.algorithm)
{
Ok(Hash {
hash,
salt,
algorithm,
})
} else {
Err("Missing fields".to_string())
}
}
}
impl Default for HashBuilder {
fn default() -> Self {
Self::new()
}
}