use md5::compute;
use md5::Digest;
use ring::digest::{digest, Digest as RingDigest, SHA256, SHA512};
use std::{error::Error, str};
const FORMATS: &[(&str, &str); 6] = &[
("md5", "$1$"),
("blf", "$2"),
("nth", "$3$"),
("sha256", "$5$"),
("sha512", "$6$"),
("des", ""),
];
pub fn crypt<'a>(password: &'a [u8], salt: &'a [u8]) -> Result<Vec<u8>, Box<Error>> {
if let Some(magic) = format_from_magic(salt) {
delegate(*magic, password, salt)
} else {
Err("cant find algorithm".into())
}
}
fn format_from_magic(salt: &[u8]) -> Option<&'static (&'static str, &'static str)> {
FORMATS
.iter()
.find(|format| salt.starts_with(&format.1.as_bytes()))
}
fn delegate<'a>(
algorithm: (&'a str, &'a str),
password: &'a [u8],
salt: &'a [u8],
) -> Result<Vec<u8>, Box<Error>> {
let digest: Result<Encrypted, Box<Error>> = match algorithm.0 {
"md5" => Ok(Encrypted::Md5(compute(password))),
"sha256" => Ok(Encrypted::Sha256(digest(&SHA256, password))),
"sha512" => Ok(Encrypted::Sha512(digest(&SHA512, password))),
_ => Err("algorithm is not supported".into()),
};
Ok(digest?.fill(salt))
}
#[derive(Clone, Debug)]
enum Encrypted {
Md5(Digest),
Sha256(RingDigest),
Sha512(RingDigest),
}
impl Encrypted {
pub fn fill<'a>(&'a self, salt: &'a [u8]) -> Vec<u8> {
let mut output = salt.to_vec();
output.push(b'$');
let digest = match *self {
Encrypted::Md5(digest) => digest.to_vec(),
Encrypted::Sha256(digest) | Encrypted::Sha512(digest) => digest.as_ref().to_vec(),
};
output.extend(digest.iter());
output
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn format_from_magic_returns_md5() {
let x = format_from_magic(String::from("$1$").as_bytes());
assert_eq!("md5", x.unwrap().0);
}
#[test]
fn md5_works() {
let password = b"abcdefghijklmnop";
let salt = b"$1$";
let digest = crypt(password, salt).unwrap();
let md5 = compute("abcdefghijklmnop");
let mut expected = salt.to_vec();
expected.push(b'$');
expected.extend(md5.to_vec().iter());
println!("{:?}", &expected);
assert_eq!(digest, expected);
}
#[test]
fn sha256_works() {
use ring::digest;
let password = b"309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f
989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76fc";
let salt = b"$5$";
let digest = crypt(password, salt).unwrap();
let sha256 = digest::digest(&digest::SHA256, &password[..]);
let mut expected = salt.to_vec();
expected.push(b'$');
expected.extend_from_slice(sha256.as_ref());
assert_eq!(digest, expected);
}
#[test]
fn sha512_works() {
use ring::digest;
let password = b"309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f
989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76fc";
let salt = b"$6$";
let digest = crypt(password, salt).unwrap();
let sha512 = digest::digest(&digest::SHA512, &password[..]);
let mut expected = salt.to_vec();
expected.push(b'$');
expected.extend_from_slice(sha512.as_ref());
assert_eq!(digest, expected);
}
}