use std::ops::RangeInclusive;
use super::md5::do_md5_crypt;
use crate::{
HashSetup, IntoHashSetup, consteq,
error::{Error, Result},
hash::{Hash, HashV},
parse::{self, HashIterator},
random,
};
const APR1_MAGIC: &str = "$apr1$";
const MAGIC_LEN: usize = APR1_MAGIC.len();
pub(crate) const HASH_LENGTH_MIN: usize = MAGIC_LEN + 0 + 1 + 22;
pub(crate) const HASH_LENGTH_MAX: usize = MAGIC_LEN + 8 + 1 + 22;
pub(crate) const HASH_LENGTH: RangeInclusive<usize> = HASH_LENGTH_MIN..=HASH_LENGTH_MAX;
pub const MAX_SALT_LEN: usize = 8;
#[deprecated(since = "0.2.0", note = "don't use this algorithm for new passwords")]
pub fn hash<B: AsRef<[u8]>>(pass: B) -> Result<Hash> {
let saltstr = random::gen_salt_str(MAX_SALT_LEN);
let hash = do_md5_crypt(pass.as_ref(), &saltstr, APR1_MAGIC)?;
Ok(Hash::Apr1(HashV(hash)))
}
fn parse_md5_hash(hash: &str) -> Result<HashSetup> {
let mut hs = parse::HashSlice::new(hash);
if hs.take(MAGIC_LEN).unwrap_or("X") != APR1_MAGIC {
return Err(Error::InvalidHashString);
}
let salt = hs.take_until(b'$').ok_or(Error::InvalidHashString)?;
Ok(HashSetup {
salt: Some(salt),
rounds: None,
})
}
#[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_md5_hash)?;
let salt = match hs.salt {
None => &random::gen_salt_str(MAX_SALT_LEN),
Some(salt) => (salt.len() <= MAX_SALT_LEN)
.then_some(salt)
.or_else(|| parse::HashSlice::new(salt).take(MAX_SALT_LEN))
.ok_or(Error::InvalidHashString)?,
};
let hash = do_md5_crypt(pass.as_ref(), salt, APR1_MAGIC)?;
Ok(Hash::Apr1(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("$apr1$63JlJ2NH$smE0mnB5h3tDri0zkpWXt1", "password").unwrap(),
"$apr1$63JlJ2NH$smE0mnB5h3tDri0zkpWXt1"
);
assert_eq!(
super::hash_with(
HashSetup {
salt: Some("63JlJ2NH"),
rounds: None
},
"password"
)
.unwrap(),
"$apr1$63JlJ2NH$smE0mnB5h3tDri0zkpWXt1"
);
}
}