use super::scrypt;
use crate::errors::CheckError;
use crate::ScryptParams;
use core::convert::TryInto;
use alloc::string::String;
use subtle::ConstantTimeEq;
use rand_core::RngCore;
#[cfg(not(features = "thread_rng"))]
type DefaultRng = rand_core::OsRng;
#[cfg(features = "thread_rng")]
type DefaultRng = rand::ThreadRng;
#[cfg(feature = "include_simple")]
pub fn scrypt_simple(password: &str, params: &ScryptParams) -> Result<String, rand_core::Error> {
let mut salt = [0u8; 16];
DefaultRng::default().try_fill_bytes(&mut salt)?;
let mut dk = [0u8; 32];
scrypt(password.as_bytes(), &salt, params, &mut dk)
.expect("32 bytes always satisfy output length requirements");
let mut result = String::with_capacity(128);
result.push_str("$rscrypt$");
if params.r < 256 && params.p < 256 {
result.push_str("0$");
let mut tmp = [0u8; 3];
tmp[0] = params.log_n;
tmp[1] = params.r as u8;
tmp[2] = params.p as u8;
result.push_str(&base64::encode(&tmp));
} else {
result.push_str("1$");
let mut tmp = [0u8; 9];
tmp[0] = params.log_n;
tmp[1..5].copy_from_slice(¶ms.r.to_le_bytes());
tmp[5..9].copy_from_slice(¶ms.p.to_le_bytes());
result.push_str(&base64::encode(&tmp));
}
result.push('$');
result.push_str(&base64::encode(&salt));
result.push('$');
result.push_str(&base64::encode(&dk));
result.push('$');
Ok(result)
}
#[cfg(feature = "include_simple")]
pub fn scrypt_check(password: &str, hashed_value: &str) -> Result<(), CheckError> {
let mut parts = hashed_value.split('$');
let buf = [
parts.next(), parts.next(), parts.next(), parts.next(),
parts.next(), parts.next(), parts.next(), parts.next(),
];
let (log_n, r, p, salt, hash) = match buf {
[
Some(""), Some("rscrypt"), Some("0"), Some(p),
Some(s), Some(h), Some(""), None
] => {
let pvec = base64::decode(p)?;
if pvec.len() != 3 {
return Err(CheckError::InvalidFormat);
}
(pvec[0], pvec[1] as u32, pvec[2] as u32, s, h)
}
[
Some(""), Some("rscrypt"), Some("1"), Some(p),
Some(s), Some(h), Some(""), None
] => {
let pvec = base64::decode(p)?;
if pvec.len() != 9 {
return Err(CheckError::InvalidFormat);
}
let log_n = pvec[0];
let r = u32::from_le_bytes(pvec[1..5].try_into().unwrap());
let p = u32::from_le_bytes(pvec[5..9].try_into().unwrap());
(log_n, r, p, s, h)
}
_ => return Err(CheckError::InvalidFormat),
};
let params = ScryptParams::new(log_n, r, p)
.map_err(|_| CheckError::InvalidFormat)?;
let salt = base64::decode(salt)?;
let hash = base64::decode(hash)?;
let mut output = vec![0u8; hash.len()];
scrypt(password.as_bytes(), &salt, ¶ms, &mut output)
.map_err(|_| CheckError::InvalidFormat)?;
if output.ct_eq(&hash).unwrap_u8() == 1 {
Ok(())
} else {
Err(CheckError::HashMismatch)?
}
}