use crate::{
HashSetup, IntoHashSetup, consteq,
encode::decode_val,
error::{Error, Result},
hash::{Hash, HashV},
internal::des::bsdi_crypt,
parse::{self, HashIterator},
random,
};
const MIN_ROUNDS: u32 = 1;
const MAX_ROUNDS: u32 = (1 << 24) - 1;
pub(crate) const HASH_LENGTH: usize = 1 + 4 + 4 + 11;
pub const DEFAULT_ROUNDS: u32 = 7250;
pub const SALT_LEN: usize = 4;
const ROUNDS_LEN: usize = 4;
#[deprecated(since = "0.2.0", note = "don't use this algorithm for new passwords")]
#[inline]
pub fn hash<B: AsRef<[u8]>>(pass: B) -> Result<Hash> {
let saltstr = random::gen_salt_str(SALT_LEN);
let hash = bsdi_crypt(pass.as_ref(), &saltstr, DEFAULT_ROUNDS)?;
Ok(Hash::Bsdi(HashV(hash)))
}
fn parse_bsdi_hash(hash: &str) -> Result<HashSetup> {
let mut hs = parse::HashSlice::new(hash);
if hs.take(1).unwrap_or("X") != "_" {
return Err(Error::InvalidHashString);
}
let rounds = hs
.take(ROUNDS_LEN)
.map(|rounds| decode_val(rounds, SALT_LEN))
.ok_or(Error::InvalidHashString)??;
let salt = hs.take(SALT_LEN).ok_or(Error::InvalidHashString)?;
Ok(HashSetup {
salt: Some(salt),
rounds: Some(rounds),
})
}
#[deprecated(since = "0.2.0", note = "don't use this algorithm for new passwords")]
pub fn hash_with<'a, IHS, B>(param: IHS, pass: B) -> Result<Hash>
where
IHS: IntoHashSetup<'a>,
B: AsRef<[u8]>,
{
let hs = IHS::into_hash_setup(param, parse_bsdi_hash)?;
let rounds = if let Some(r) = hs.rounds {
if !(MIN_ROUNDS..=MAX_ROUNDS).contains(&r) {
return Err(Error::InvalidRounds);
}
r
} else {
DEFAULT_ROUNDS
};
let hash = match hs.salt {
Some(salt) => bsdi_crypt(pass.as_ref(), salt, rounds),
None => {
let saltstr = random::gen_salt_str(SALT_LEN);
bsdi_crypt(pass.as_ref(), &saltstr, rounds)
}
}?;
Ok(Hash::Bsdi(HashV(hash)))
}
#[inline]
pub fn verify<B: AsRef<[u8]>>(pass: B, hash: &str) -> bool {
#[allow(deprecated)]
consteq(hash, hash_with(hash, pass))
}
#[cfg(test)]
mod tests {
use super::HashSetup;
#[test]
#[allow(deprecated)]
fn custom() {
assert_eq!(
super::hash_with(
HashSetup {
salt: Some("K0Ay"),
rounds: None
},
"password"
)
.unwrap(),
"_Gl/.K0Ay.aosctsbJ1k"
);
assert_eq!(
super::hash_with("_Gl/.K0Ay.aosctsbJ1k", "password").unwrap(),
"_Gl/.K0Ay.aosctsbJ1k"
);
}
#[test]
#[allow(deprecated)]
#[should_panic(expected = "value: InvalidRounds")]
fn bad_rounds() {
let _ = super::hash_with(
HashSetup {
salt: Some("K0Ay"),
rounds: Some(0),
},
"password",
)
.unwrap();
}
}