193 lines
5.5 KiB
Rust
193 lines
5.5 KiB
Rust
use crate::ctx::Context;
|
|
use crate::err::Error;
|
|
use crate::sql::value::Value;
|
|
use md5::Digest;
|
|
use md5::Md5;
|
|
use sha1::Sha1;
|
|
use sha2::Sha256;
|
|
use sha2::Sha512;
|
|
|
|
pub fn md5(_: &Context, mut args: Vec<Value>) -> Result<Value, Error> {
|
|
let mut hasher = Md5::new();
|
|
hasher.update(args.remove(0).as_string().as_str());
|
|
let val = hasher.finalize();
|
|
let val = format!("{:x}", val);
|
|
Ok(val.into())
|
|
}
|
|
|
|
pub fn sha1(_: &Context, mut args: Vec<Value>) -> Result<Value, Error> {
|
|
let mut hasher = Sha1::new();
|
|
hasher.update(args.remove(0).as_string().as_str());
|
|
let val = hasher.finalize();
|
|
let val = format!("{:x}", val);
|
|
Ok(val.into())
|
|
}
|
|
|
|
pub fn sha256(_: &Context, mut args: Vec<Value>) -> Result<Value, Error> {
|
|
let mut hasher = Sha256::new();
|
|
hasher.update(args.remove(0).as_string().as_str());
|
|
let val = hasher.finalize();
|
|
let val = format!("{:x}", val);
|
|
Ok(val.into())
|
|
}
|
|
|
|
pub fn sha512(_: &Context, mut args: Vec<Value>) -> Result<Value, Error> {
|
|
let mut hasher = Sha512::new();
|
|
hasher.update(args.remove(0).as_string().as_str());
|
|
let val = hasher.finalize();
|
|
let val = format!("{:x}", val);
|
|
Ok(val.into())
|
|
}
|
|
|
|
/// Allowed to cost this much more than default setting for each hash function.
|
|
const COST_ALLOWANCE: u32 = 4;
|
|
|
|
/// Like verify_password, but takes a closure to determine whether the cost of performing the
|
|
/// operation is not too high.
|
|
macro_rules! bounded_verify_password {
|
|
($algo: ident, $instance: expr, $password: expr, $hash: expr, $bound: expr) => {
|
|
if let (Some(salt), Some(expected_output)) = (&$hash.salt, &$hash.hash) {
|
|
if let Some(params) =
|
|
<$algo as PasswordHasher>::Params::try_from($hash).ok().filter($bound)
|
|
{
|
|
if let Ok(computed_hash) = $instance.hash_password_customized(
|
|
$password.as_ref(),
|
|
Some($hash.algorithm),
|
|
$hash.version,
|
|
params,
|
|
*salt,
|
|
) {
|
|
if let Some(computed_output) = &computed_hash.hash {
|
|
expected_output == computed_output
|
|
} else {
|
|
false
|
|
}
|
|
} else {
|
|
false
|
|
}
|
|
} else {
|
|
false
|
|
}
|
|
} else {
|
|
false
|
|
}
|
|
};
|
|
|
|
($algo: ident, $password: expr, $hash: expr, $bound: expr) => {
|
|
bounded_verify_password!($algo, $algo::default(), $password, $hash, $bound)
|
|
};
|
|
}
|
|
|
|
pub mod argon2 {
|
|
|
|
use super::COST_ALLOWANCE;
|
|
use crate::ctx::Context;
|
|
use crate::err::Error;
|
|
use crate::sql::value::Value;
|
|
use argon2::{
|
|
password_hash::{PasswordHash, PasswordHasher, SaltString},
|
|
Argon2,
|
|
};
|
|
use rand::rngs::OsRng;
|
|
|
|
pub fn cmp(_: &Context, args: Vec<Value>) -> Result<Value, Error> {
|
|
let args: [Value; 2] = args.try_into().unwrap();
|
|
let [hash, pass] = args.map(Value::as_string);
|
|
type Params<'a> = <Argon2<'a> as PasswordHasher>::Params;
|
|
Ok(PasswordHash::new(&hash)
|
|
.ok()
|
|
.filter(|test| {
|
|
bounded_verify_password!(Argon2, pass, test, |params: &Params| {
|
|
params.m_cost() <= Params::DEFAULT_M_COST.saturating_mul(COST_ALLOWANCE)
|
|
&& params.t_cost() <= Params::DEFAULT_T_COST.saturating_mul(COST_ALLOWANCE)
|
|
&& params.p_cost() <= Params::DEFAULT_P_COST.saturating_mul(COST_ALLOWANCE)
|
|
})
|
|
})
|
|
.is_some()
|
|
.into())
|
|
}
|
|
|
|
pub fn gen(_: &Context, mut args: Vec<Value>) -> Result<Value, Error> {
|
|
let algo = Argon2::default();
|
|
let pass = args.remove(0).as_string();
|
|
let salt = SaltString::generate(&mut OsRng);
|
|
let hash = algo.hash_password(pass.as_ref(), salt.as_ref()).unwrap().to_string();
|
|
Ok(hash.into())
|
|
}
|
|
}
|
|
|
|
pub mod pbkdf2 {
|
|
|
|
use super::COST_ALLOWANCE;
|
|
use crate::ctx::Context;
|
|
use crate::err::Error;
|
|
use crate::sql::value::Value;
|
|
use pbkdf2::{
|
|
password_hash::{PasswordHash, PasswordHasher, SaltString},
|
|
Pbkdf2,
|
|
};
|
|
use rand::rngs::OsRng;
|
|
|
|
pub fn cmp(_: &Context, args: Vec<Value>) -> Result<Value, Error> {
|
|
let args: [Value; 2] = args.try_into().unwrap();
|
|
let [hash, pass] = args.map(Value::as_string);
|
|
type Params = <Pbkdf2 as PasswordHasher>::Params;
|
|
Ok(PasswordHash::new(&hash)
|
|
.ok()
|
|
.filter(|test| {
|
|
bounded_verify_password!(Pbkdf2, Pbkdf2, pass, test, |params: &Params| {
|
|
params.rounds <= Params::default().rounds.saturating_mul(COST_ALLOWANCE)
|
|
&& params.output_length
|
|
<= Params::default()
|
|
.output_length
|
|
.saturating_mul(COST_ALLOWANCE as usize)
|
|
})
|
|
})
|
|
.is_some()
|
|
.into())
|
|
}
|
|
|
|
pub fn gen(_: &Context, mut args: Vec<Value>) -> Result<Value, Error> {
|
|
let pass = args.remove(0).as_string();
|
|
let salt = SaltString::generate(&mut OsRng);
|
|
let hash = Pbkdf2.hash_password(pass.as_ref(), salt.as_ref()).unwrap().to_string();
|
|
Ok(hash.into())
|
|
}
|
|
}
|
|
|
|
pub mod scrypt {
|
|
|
|
use crate::ctx::Context;
|
|
use crate::err::Error;
|
|
use crate::sql::value::Value;
|
|
use rand::rngs::OsRng;
|
|
use scrypt::{
|
|
password_hash::{PasswordHash, PasswordHasher, SaltString},
|
|
Scrypt,
|
|
};
|
|
|
|
pub fn cmp(_: &Context, args: Vec<Value>) -> Result<Value, Error> {
|
|
let args: [Value; 2] = args.try_into().unwrap();
|
|
let [hash, pass] = args.map(Value::as_string);
|
|
type Params = <Scrypt as PasswordHasher>::Params;
|
|
Ok(PasswordHash::new(&hash)
|
|
.ok()
|
|
.filter(|test| {
|
|
bounded_verify_password!(Scrypt, Scrypt, pass, test, |params: &Params| {
|
|
// Scrypt is slow, use lower cost allowance.
|
|
params.log_n() <= Params::default().log_n().saturating_add(2)
|
|
&& params.r() <= Params::default().r().saturating_mul(2)
|
|
&& params.p() <= Params::default().p().saturating_mul(4)
|
|
})
|
|
})
|
|
.is_some()
|
|
.into())
|
|
}
|
|
|
|
pub fn gen(_: &Context, mut args: Vec<Value>) -> Result<Value, Error> {
|
|
let pass = args.remove(0).as_string();
|
|
let salt = SaltString::generate(&mut OsRng);
|
|
let hash = Scrypt.hash_password(pass.as_ref(), salt.as_ref()).unwrap().to_string();
|
|
Ok(hash.into())
|
|
}
|
|
}
|