pub fn md5_password(password: &str, username: &str, salt: [u8; 4]) -> String {
StoredHash::generate(password.as_bytes(), username).salted(salt)
}
fn to_hex_string(bytes: &[u8]) -> String {
let mut hex = String::with_capacity(bytes.len() * 2);
for &byte in bytes {
hex.push_str(&format!("{byte:02x}"));
}
hex
}
#[derive(Clone, Copy, Debug)]
pub struct StoredHash {
pub hash: [u8; 16],
}
impl StoredHash {
pub fn generate(password: &[u8], username: &str) -> Self {
let mut hasher = md5::Context::new();
hasher.consume(password);
hasher.consume(username.as_bytes());
let first_hash = hasher.compute();
Self { hash: first_hash.0 }
}
pub fn matches(&self, client_exchange: &[u8], salt: [u8; 4]) -> bool {
let server_exchange = self.salted(salt);
constant_time_eq::constant_time_eq(client_exchange, server_exchange.as_bytes())
}
pub fn salted(&self, salt: [u8; 4]) -> String {
let this = &self;
let salt: &[u8; 4] = &salt;
let first_hash_hex = to_hex_string(&this.hash);
let mut hasher = md5::Context::new();
hasher.consume(first_hash_hex.as_bytes());
hasher.consume(salt);
let second_hash = hasher.compute();
let second_hash_hex = to_hex_string(&second_hash.0);
format!("md5{second_hash_hex}")
}
}
impl PartialEq for StoredHash {
fn eq(&self, other: &Self) -> bool {
constant_time_eq::constant_time_eq(&self.hash, &other.hash)
}
}
impl Eq for StoredHash {}