use md5::{Digest, Md5};
const HASH_PREFIX: &[u8] = b"$apr1$";
const DIGEST_SIZE: usize = 16;
fn extract_salt(mut hash: &[u8]) -> Option<&[u8]> {
use std::cmp::min;
if !hash.starts_with(HASH_PREFIX) {
return None;
}
hash = &hash[HASH_PREFIX.len()..];
hash = &hash[..min(hash.len(), 8)];
let end = hash.iter().position(|&x| x == b'$').unwrap_or(8);
Some(&hash[..end])
}
pub fn verify(password: &[u8], hash: &[u8]) -> bool {
let Some(salt) = extract_salt(hash) else {
return false;
};
assert!(salt.len() == 8);
let salt = salt.try_into().unwrap();
let h2 = to_hash_string(encode(password, salt), salt);
return hash == h2;
}
pub fn to_hash_string(digest: [u8; DIGEST_SIZE], salt: [u8; 8]) -> Vec<u8> {
const LEN_STRING: usize = 37;
const LEN_HASH: usize = 22;
let mut out = Vec::with_capacity(LEN_STRING);
out.extend_from_slice(HASH_PREFIX);
out.extend_from_slice(&salt);
out.push(b'$');
let alpha = b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
let n = u128::from_le_bytes(digest);
for i in 0..LEN_HASH {
let idx = (n >> i * 6) as usize & 0x3F;
out.push(alpha[idx]);
}
out
}
pub fn encode(password: &[u8], salt: [u8; 8]) -> [u8; DIGEST_SIZE] {
let mut md5 = Md5::new();
md5.update(password);
md5.update(HASH_PREFIX);
md5.update(&salt);
let extra: [u8; DIGEST_SIZE] = {
let mut md5 = Md5::new();
md5.update(password);
md5.update(&salt);
md5.update(password);
md5.finalize().into()
};
for _ in 0..password.len() / DIGEST_SIZE {
md5.update(&extra);
}
md5.update(&extra[..password.len() % DIGEST_SIZE]);
let mut i = password.len();
while i != 0 {
if i & 1 != 0 {
md5.update(&[0]);
}
else {
md5.update(password.get(..1).unwrap_or(&[0]));
}
i >>= 1;
}
let mut digest: [u8; DIGEST_SIZE] = md5.finalize().into();
for i in 0..1000 {
let mut md5 = Md5::new();
if i & 1 != 0 {
md5.update(password);
}
else {
md5.update(&digest);
}
if i % 3 != 0 {
md5.update(&salt);
}
if i % 7 != 0 {
md5.update(password);
}
if i & 1 != 0 {
md5.update(&digest)
}
else {
md5.update(password);
}
digest = md5.finalize().into();
}
let output = [
digest[12], digest[ 6], digest[0],
digest[13], digest[ 7], digest[1],
digest[14], digest[ 8], digest[2],
digest[15], digest[ 9], digest[3],
digest[ 5], digest[10], digest[4],
digest[11]
];
output
}