use crate::crypto_utils;
use std::str;
#[cfg(feature = "with_argon2")]
use base64::engine::general_purpose;
#[cfg(feature = "with_argon2")]
use base64::engine::Engine as _;
#[cfg(feature = "with_pbkdf2")]
static PBKDF2_ITERATIONS_DOS_LIMIT: u32 = 1_000_000;
#[cfg(feature = "with_bcrypt")]
static BCRYPT_COST_DOS_LIMIT: u32 = 16;
#[derive(PartialEq, Debug)]
pub enum HasherError {
UnknownAlgorithm,
BadHash,
EmptyHash,
InvalidIterations,
InvalidArgon2Salt,
}
pub trait Hasher {
fn verify(&self, password: &str, encoded: &str) -> Result<bool, HasherError>;
fn encode(&self, password: &str, salt: &str, iterations: u32) -> String;
}
#[cfg(feature = "with_pbkdf2")]
pub struct PBKDF2Hasher;
#[cfg(feature = "with_pbkdf2")]
impl Hasher for PBKDF2Hasher {
fn verify(&self, password: &str, encoded: &str) -> Result<bool, HasherError> {
let mut encoded_part = encoded.split('$').skip(1);
let iterations = encoded_part
.next()
.ok_or(HasherError::BadHash)?
.parse::<u32>()
.map_err(|_| HasherError::InvalidIterations)?;
if iterations > PBKDF2_ITERATIONS_DOS_LIMIT {
return Err(HasherError::InvalidIterations);
}
let salt = encoded_part.next().ok_or(HasherError::BadHash)?;
let hash = encoded_part.next().ok_or(HasherError::BadHash)?;
Ok(crypto_utils::safe_eq(
hash,
crypto_utils::hash_pbkdf2_sha256(password, salt, iterations),
))
}
fn encode(&self, password: &str, salt: &str, iterations: u32) -> String {
let hash = crypto_utils::hash_pbkdf2_sha256(password, salt, iterations);
format!("{}${}${}${}", "pbkdf2_sha256", iterations, salt, hash)
}
}
#[cfg(feature = "with_pbkdf2")]
pub struct PBKDF2SHA1Hasher;
#[cfg(feature = "with_pbkdf2")]
impl Hasher for PBKDF2SHA1Hasher {
fn verify(&self, password: &str, encoded: &str) -> Result<bool, HasherError> {
let mut encoded_part = encoded.split('$').skip(1);
let iterations = encoded_part
.next()
.ok_or(HasherError::BadHash)?
.parse::<u32>()
.map_err(|_| HasherError::InvalidIterations)?;
if iterations > PBKDF2_ITERATIONS_DOS_LIMIT {
return Err(HasherError::InvalidIterations);
}
let salt = encoded_part.next().ok_or(HasherError::BadHash)?;
let hash = encoded_part.next().ok_or(HasherError::BadHash)?;
Ok(crypto_utils::safe_eq(
hash,
crypto_utils::hash_pbkdf2_sha1(password, salt, iterations),
))
}
fn encode(&self, password: &str, salt: &str, iterations: u32) -> String {
let hash = crypto_utils::hash_pbkdf2_sha1(password, salt, iterations);
format!("{}${}${}${}", "pbkdf2_sha1", iterations, salt, hash)
}
}
#[cfg(feature = "with_argon2")]
pub struct Argon2Hasher;
#[cfg(feature = "with_argon2")]
use argon2::{self, Version};
#[cfg(feature = "with_argon2")]
impl Hasher for Argon2Hasher {
fn verify(&self, password: &str, encoded: &str) -> Result<bool, HasherError> {
let encoded_part: Vec<&str> = encoded.split('$').collect();
let version = match encoded_part.len() {
6 => Version::Version13,
5 => Version::Version10,
_ => return Err(HasherError::BadHash),
};
let segment_shift = 6 - encoded_part.len();
let settings = encoded_part[3 - segment_shift];
let salt = encoded_part[4 - segment_shift];
let string_hash = encoded_part[5 - segment_shift].replace('+', "-");
let hash = string_hash.as_str();
let settings_part: Vec<&str> = settings.split(',').collect();
let memory_cost: u32 = settings_part[0].split('=').collect::<Vec<&str>>()[1]
.parse::<u32>()
.map_err(|_| HasherError::BadHash)?;
let time_cost: u32 = settings_part[1].split('=').collect::<Vec<&str>>()[1]
.parse::<u32>()
.map_err(|_| HasherError::BadHash)?;
let parallelism: u32 = settings_part[2].split('=').collect::<Vec<&str>>()[1]
.parse::<u32>()
.map_err(|_| HasherError::BadHash)?;
if general_purpose::URL_SAFE_NO_PAD.decode(hash).is_err() {
return Err(HasherError::InvalidArgon2Salt);
}
let hash_length = match general_purpose::URL_SAFE_NO_PAD.decode(hash) {
Ok(value) => value.len() as u32,
Err(_) => return Ok(false),
};
Ok(crypto_utils::safe_eq(
hash,
crypto_utils::hash_argon2(
password,
salt,
time_cost,
memory_cost,
parallelism,
version,
hash_length,
),
))
}
fn encode(&self, password: &str, salt: &str, iterations: u32) -> String {
let (memory_cost, parallelism, time_cost) = match iterations {
1 => (512, 2, 2),
2 => (102400, 8, 2),
_ => unreachable!(),
};
let version = Version::Version13;
let hash_length: u32 = 16;
let hash = crypto_utils::hash_argon2(
password,
salt,
time_cost,
memory_cost,
parallelism,
version,
hash_length,
);
format!(
"argon2$argon2id$v=19$m={},t={},p={}${}${}",
memory_cost, time_cost, parallelism, salt, hash
)
}
}
#[cfg(feature = "with_scrypt")]
pub struct ScryptHasher;
#[cfg(feature = "with_scrypt")]
impl Hasher for ScryptHasher {
fn verify(&self, password: &str, encoded: &str) -> Result<bool, HasherError> {
let encoded_part: Vec<&str> = encoded.split('$').collect();
let work_factor: u8 = encoded_part[1].parse::<f32>().unwrap().log2() as u8;
let salt = encoded_part[2];
let block_size = encoded_part[3].parse::<u32>().unwrap();
let parallelism = encoded_part[4].parse::<u32>().unwrap();
let hash = encoded_part[5];
Ok(crypto_utils::safe_eq(
hash,
crypto_utils::hash_scrypt(password, salt, work_factor, block_size, parallelism),
))
}
fn encode(&self, password: &str, salt: &str, iterations: u32) -> String {
let (work_factor, block_size, parallelism) = match iterations {
1 => (14, 8, 1),
_ => unreachable!(),
};
let hash = crypto_utils::hash_scrypt(password, salt, work_factor, block_size, parallelism);
format!(
"scrypt${}${}${}${}${}",
2i32.pow(work_factor as u32),
salt,
block_size,
parallelism,
hash
)
}
}
#[cfg(feature = "with_bcrypt")]
pub struct BCryptSHA256Hasher;
#[cfg(feature = "with_bcrypt")]
impl Hasher for BCryptSHA256Hasher {
fn verify(&self, password: &str, encoded: &str) -> Result<bool, HasherError> {
let bcrypt_encoded_part: Vec<&str> = encoded.splitn(2, '$').collect();
let cost = bcrypt_encoded_part[1]
.split('$')
.nth(2)
.ok_or(HasherError::BadHash)?
.parse::<u32>()
.map_err(|_| HasherError::InvalidIterations)?;
if cost > BCRYPT_COST_DOS_LIMIT {
return Err(HasherError::InvalidIterations);
}
let hash = bcrypt_encoded_part[1];
let hashed_password = crypto_utils::hash_sha256(password);
Ok(bcrypt::verify(hashed_password, hash).unwrap_or(false))
}
fn encode(&self, password: &str, _: &str, iterations: u32) -> String {
let hashed_password = crypto_utils::hash_sha256(password);
let hash = bcrypt::hash(hashed_password, iterations).unwrap();
format!("{}${}", "bcrypt_sha256", hash)
}
}
#[cfg(feature = "with_bcrypt")]
pub struct BCryptHasher;
#[cfg(feature = "with_bcrypt")]
impl Hasher for BCryptHasher {
fn verify(&self, password: &str, encoded: &str) -> Result<bool, HasherError> {
let bcrypt_encoded_part: Vec<&str> = encoded.splitn(2, '$').collect();
let cost = bcrypt_encoded_part[1]
.split('$')
.nth(2)
.ok_or(HasherError::BadHash)?
.parse::<u32>()
.map_err(|_| HasherError::InvalidIterations)?;
if cost > BCRYPT_COST_DOS_LIMIT {
return Err(HasherError::InvalidIterations);
}
let hash = bcrypt_encoded_part[1];
Ok(bcrypt::verify(password, hash).unwrap_or(false))
}
fn encode(&self, password: &str, _: &str, iterations: u32) -> String {
let hash = bcrypt::hash(password, iterations).unwrap();
format!("{}${}", "bcrypt", hash)
}
}
#[cfg(feature = "with_legacy")]
pub struct SHA1Hasher;
#[cfg(feature = "with_legacy")]
impl Hasher for SHA1Hasher {
fn verify(&self, password: &str, encoded: &str) -> Result<bool, HasherError> {
let mut encoded_part = encoded.split('$').skip(1);
let salt = encoded_part.next().ok_or(HasherError::BadHash)?;
let hash = encoded_part.next().ok_or(HasherError::BadHash)?;
Ok(crypto_utils::safe_eq(
hash,
crypto_utils::hash_sha1(password, salt),
))
}
fn encode(&self, password: &str, salt: &str, _: u32) -> String {
let hash = crypto_utils::hash_sha1(password, salt);
format!("{}${}${}", "sha1", salt, hash)
}
}
#[cfg(feature = "with_legacy")]
pub struct MD5Hasher;
#[cfg(feature = "with_legacy")]
impl Hasher for MD5Hasher {
fn verify(&self, password: &str, encoded: &str) -> Result<bool, HasherError> {
let mut encoded_part = encoded.split('$').skip(1);
let salt = encoded_part.next().ok_or(HasherError::BadHash)?;
let hash = encoded_part.next().ok_or(HasherError::BadHash)?;
Ok(crypto_utils::safe_eq(
hash,
crypto_utils::hash_md5(password, salt),
))
}
fn encode(&self, password: &str, salt: &str, _: u32) -> String {
let hash = crypto_utils::hash_md5(password, salt);
format!("{}${}${}", "md5", salt, hash)
}
}
#[cfg(feature = "with_legacy")]
pub struct UnsaltedSHA1Hasher;
#[cfg(feature = "with_legacy")]
impl Hasher for UnsaltedSHA1Hasher {
fn verify(&self, password: &str, encoded: &str) -> Result<bool, HasherError> {
let mut encoded_part = encoded.split('$').skip(2);
let hash = encoded_part.next().ok_or(HasherError::BadHash)?;
Ok(crypto_utils::safe_eq(
hash,
crypto_utils::hash_sha1(password, ""),
))
}
fn encode(&self, password: &str, _: &str, _: u32) -> String {
let hash = crypto_utils::hash_sha1(password, "");
format!("{}$${}", "sha1", hash)
}
}
#[cfg(feature = "with_legacy")]
pub struct UnsaltedMD5Hasher;
#[cfg(feature = "with_legacy")]
impl Hasher for UnsaltedMD5Hasher {
fn verify(&self, password: &str, encoded: &str) -> Result<bool, HasherError> {
Ok(crypto_utils::safe_eq(
encoded,
crypto_utils::hash_md5(password, ""),
))
}
fn encode(&self, password: &str, _: &str, _: u32) -> String {
crypto_utils::hash_md5(password, "")
}
}
#[cfg(feature = "with_legacy")]
pub struct CryptHasher;
#[cfg(feature = "with_legacy")]
impl Hasher for CryptHasher {
fn verify(&self, password: &str, encoded: &str) -> Result<bool, HasherError> {
let mut encoded_part = encoded.split('$').skip(2);
let hash = encoded_part.next().ok_or(HasherError::BadHash)?;
Ok(crypto_utils::safe_eq(
hash,
crypto_utils::hash_unix_crypt(password, hash),
))
}
fn encode(&self, password: &str, salt: &str, _: u32) -> String {
let hash = crypto_utils::hash_unix_crypt(password, salt);
format!("{}$${}", "crypt", hash)
}
}