use rand::Rng;
use ring::digest::{Context, SHA256, SHA512};
use std::fmt;
use thiserror::Error;
const SALT_LENGTH: usize = 4;
pub fn salt() -> Vec<u8> {
let mut buf: [u8; SALT_LENGTH] = [0; SALT_LENGTH];
let mut rng = rand::rng();
rng.fill_bytes(&mut buf);
Vec::from(&buf)
}
pub fn salted_password_hash_sha256(salt: &[u8], password: &str) -> Vec<u8> {
salted_password_hash(salt, password, &SHA256)
}
pub fn salted_password_hash_sha512(salt: &[u8], password: &str) -> Vec<u8> {
salted_password_hash(salt, password, &SHA512)
}
pub fn base64_encoded_salted_password_hash(
salt: &[u8],
password: &str,
algorithm: &HashingAlgorithm,
) -> String {
let salted = match algorithm {
HashingAlgorithm::SHA256 => salted_password_hash_sha256(salt, password),
HashingAlgorithm::SHA512 => salted_password_hash_sha512(salt, password),
};
rbase64::encode(salted.as_slice())
}
pub fn base64_encoded_salted_password_hash_sha256(salt: &[u8], password: &str) -> String {
base64_encoded_salted_password_hash(salt, password, &HashingAlgorithm::SHA256)
}
pub fn base64_encoded_salted_password_hash_sha512(salt: &[u8], password: &str) -> String {
base64_encoded_salted_password_hash(salt, password, &HashingAlgorithm::SHA512)
}
#[derive(Clone, Default, PartialEq, Eq, Hash, Debug)]
pub enum HashingAlgorithm {
#[default]
SHA256,
SHA512,
}
impl fmt::Display for HashingAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HashingAlgorithm::SHA256 => write!(f, "SHA-256"),
HashingAlgorithm::SHA512 => write!(f, "SHA-512"),
}
}
}
impl From<&str> for HashingAlgorithm {
fn from(s: &str) -> Self {
match s.to_uppercase().as_str() {
"SHA256" => HashingAlgorithm::SHA256,
"SHA-256" => HashingAlgorithm::SHA256,
"SHA512" => HashingAlgorithm::SHA512,
"SHA-512" => HashingAlgorithm::SHA512,
_ => HashingAlgorithm::default(),
}
}
}
impl From<String> for HashingAlgorithm {
fn from(s: String) -> Self {
HashingAlgorithm::from(s.as_str())
}
}
#[derive(Error, Debug)]
pub enum HashingError {
#[error("Provided algorithm is not supported")]
UnsupportedAlgorithm,
}
impl HashingAlgorithm {
pub fn salt_and_hash(&self, salt: &[u8], password: &str) -> Result<String, HashingError> {
Ok(base64_encoded_salted_password_hash(salt, password, self))
}
}
fn salted_password_hash(
salt: &[u8],
password: &str,
algo: &'static ring::digest::Algorithm,
) -> Vec<u8> {
let mut ctx = Context::new(algo);
let vec = [salt, password.as_bytes()].concat();
ctx.update(&vec);
let digest = ctx.finish();
let digest_vec = Vec::from(digest.as_ref());
[salt, &digest_vec[..]].concat()
}