pub use mcf::{PasswordHash, PasswordHashRef};
use crate::{Params, Scrypt, scrypt};
use alloc::{string::String, vec};
use core::str;
use mcf::Base64;
use password_hash::{
CustomizedPasswordHasher, Error, PasswordHasher, PasswordVerifier, Result, Version,
};
#[cfg(feature = "password-hash")]
const MCF_ID: &str = "7";
const SCRYPT_BASE64: Base64 = Base64::Crypt;
const ENCODED_U32_LEN: usize = 5;
const PARAMS_LEN: usize = 1 + (2 * ENCODED_U32_LEN);
impl CustomizedPasswordHasher<PasswordHash> for Scrypt {
type Params = Params;
fn hash_password_customized(
&self,
password: &[u8],
salt: &[u8],
alg_id: Option<&str>,
version: Option<Version>,
params: Params,
) -> Result<PasswordHash> {
const HASH_SIZE: usize = 32;
match alg_id {
Some(MCF_ID) | None => (),
_ => return Err(Error::Algorithm),
}
if version.is_some() {
return Err(Error::Version);
}
let params_and_salt = encode_params_and_salt(params, salt)?;
let salt = ¶ms_and_salt.as_bytes()[PARAMS_LEN..];
let mut out = [0u8; HASH_SIZE];
scrypt(password, salt, ¶ms, &mut out).map_err(|_| Error::OutputSize)?;
let mut mcf_hash = PasswordHash::from_id(MCF_ID).expect("should be valid");
mcf_hash
.push_str(¶ms_and_salt)
.map_err(|_| Error::EncodingInvalid)?;
mcf_hash.push_base64(&out, SCRYPT_BASE64);
Ok(mcf_hash)
}
}
impl PasswordHasher<PasswordHash> for Scrypt {
fn hash_password_with_salt(&self, password: &[u8], salt: &[u8]) -> Result<PasswordHash> {
self.hash_password_customized(password, salt, None, None, self.params)
}
}
impl PasswordVerifier<PasswordHash> for Scrypt {
fn verify_password(&self, password: &[u8], hash: &PasswordHash) -> Result<()> {
self.verify_password(password, hash.as_password_hash_ref())
}
}
impl PasswordVerifier<PasswordHashRef> for Scrypt {
fn verify_password(&self, password: &[u8], hash: &PasswordHashRef) -> Result<()> {
if hash.id() != MCF_ID {
return Err(Error::Algorithm);
}
let mut fields = hash.fields();
let (params, salt) =
decode_params_and_salt(fields.next().ok_or(Error::EncodingInvalid)?.as_str())?;
let expected = fields
.next()
.ok_or(Error::EncodingInvalid)?
.decode_base64(SCRYPT_BASE64)
.map_err(|_| Error::EncodingInvalid)?;
if fields.next().is_some() {
return Err(Error::EncodingInvalid);
}
let mut actual = vec![0u8; expected.len()];
scrypt(password, salt, ¶ms, &mut actual).map_err(|_| Error::OutputSize)?;
if ctutils::CtEq::ct_ne(actual.as_slice(), &expected).into() {
return Err(Error::PasswordInvalid);
}
Ok(())
}
}
static ITOA64: &[u8] = b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static ATOI64: [u8; 128] = {
let mut tbl = [0xFFu8; 128]; let mut i = 0u8;
while i < 64 {
tbl[ITOA64[i as usize] as usize] = i;
i += 1;
}
tbl
};
fn decode_params_and_salt(s: &str) -> Result<(Params, &[u8])> {
let bytes = s.as_bytes();
if bytes.is_empty() {
return Err(Error::EncodingInvalid);
}
let log_n = *ATOI64
.get(bytes[0] as usize)
.ok_or(Error::EncodingInvalid)?;
let mut pos = 1;
let r = decode64_uint32(&bytes[pos..])?;
pos += ENCODED_U32_LEN;
let p = decode64_uint32(&bytes[pos..])?;
pos += ENCODED_U32_LEN;
let params = Params::new(log_n, r, p).map_err(|_| Error::ParamsInvalid)?;
Ok((params, &s.as_bytes()[pos..]))
}
fn encode_params_and_salt(params: Params, salt: &[u8]) -> Result<String> {
let mut buf = [0u8; PARAMS_LEN];
let params_base64 = encode_params(params, &mut buf)?;
let mut ret = String::from(params_base64);
ret.push_str(&SCRYPT_BASE64.encode_string(salt));
Ok(ret)
}
fn encode_params(params: Params, out: &mut [u8]) -> Result<&str> {
let encoded_log_n = *ITOA64
.get(params.log_n as usize)
.ok_or(Error::EncodingInvalid)?;
*out.get_mut(0).ok_or(Error::EncodingInvalid)? = encoded_log_n;
let mut pos = 1;
encode64_uint32(&mut out[pos..], params.r())?;
pos += ENCODED_U32_LEN;
encode64_uint32(&mut out[pos..], params.p())?;
pos += ENCODED_U32_LEN;
str::from_utf8(&out[..pos]).map_err(|_| Error::EncodingInvalid)
}
fn decode64_uint32(src: &[u8]) -> Result<u32> {
let mut value: u32 = 0;
for i in 0..ENCODED_U32_LEN {
let n = *src
.get(i)
.and_then(|&b| ATOI64.get(b as usize).filter(|&&n| n <= 63))
.ok_or(Error::EncodingInvalid)?;
value |= u32::from(n) << (6 * i);
}
Ok(value)
}
fn encode64_uint32(dst: &mut [u8], mut src: u32) -> Result<()> {
if dst.len() < 5 {
return Err(Error::EncodingInvalid);
}
#[allow(clippy::needless_range_loop)]
for i in 0..ENCODED_U32_LEN {
dst[i] = ITOA64[(src & 0x3f) as usize];
src >>= 6;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::{
CustomizedPasswordHasher, Error, Params, PasswordHash, PasswordHashRef, PasswordVerifier,
SCRYPT_BASE64, Scrypt, decode_params_and_salt,
};
const EXAMPLE_PASSWORD: &[u8] = b"pleaseletmein";
const EXAMPLE_SALT: &str = "Mq4YHD2syxYT.MsH1Ek0n1";
const EXAMPLE_MCF_HASH: &str =
"$7$CU..../....Mq4YHD2syxYT.MsH1Ek0n1$JyHIxez0DOwm0r6.kAIohc8UFBOLU4xX8a1wGBpLrw7";
const EXAMPLE_LOG_N: u8 = 14; const EXAMPLE_R: u32 = 32; const EXAMPLE_P: u32 = 1;
#[test]
fn params_and_salt_decoder() {
let mut mcf_iter = EXAMPLE_MCF_HASH.split('$');
mcf_iter.next().unwrap();
mcf_iter.next().unwrap();
let params_and_salt = mcf_iter.next().unwrap();
let (params, salt) = decode_params_and_salt(params_and_salt).unwrap();
assert_eq!(params.p(), EXAMPLE_P);
assert_eq!(params.r(), EXAMPLE_R);
assert_eq!(params.log_n(), EXAMPLE_LOG_N);
assert_eq!(salt, EXAMPLE_SALT.as_bytes());
}
#[test]
fn hash_password() {
let salt = SCRYPT_BASE64.decode_vec(EXAMPLE_SALT).unwrap();
let params = Params::new(EXAMPLE_LOG_N, EXAMPLE_R, EXAMPLE_P).unwrap();
let actual_hash: PasswordHash = Scrypt::new()
.hash_password_with_params(EXAMPLE_PASSWORD, &salt, params)
.unwrap();
let expected_hash = PasswordHash::new(EXAMPLE_MCF_HASH).unwrap();
assert_eq!(expected_hash, actual_hash);
}
#[test]
fn verify_password() {
let hash = PasswordHashRef::new(EXAMPLE_MCF_HASH).unwrap();
assert_eq!(
Scrypt::new().verify_password(EXAMPLE_PASSWORD, hash),
Ok(())
);
assert_eq!(
Scrypt::new().verify_password(b"bogus", hash),
Err(Error::PasswordInvalid)
);
}
}