use rand::{thread_rng, RngCore};
use ring::{self, digest};
use std::collections::HashMap;
use std::num::NonZeroU32;
#[macro_use]
mod phc_encoding;
use self::phc_encoding::PHCEncoded;
pub const PASSWORD_MIN_LEN: usize = 4;
pub const PASSWORD_MAX_LEN: usize = 128;
fn to_hex(v: Vec<u8>) -> String {
let mut s = "".to_string();
for e in v.iter() {
s += format!("{:02x}", e).as_str();
}
s
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub enum ErrorCode {
Success = 0,
PasswordTooShort = 1,
PasswordTooLong = 2,
InvalidPasswordFormat = 10,
NotEnoughSpace = 20,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub enum HashFunction {
Sha256 = 2,
Sha512 = 3,
}
fn generate_salt(nb_bytes: usize) -> Vec<u8> {
let mut salt: Vec<u8> = vec![0; nb_bytes];
thread_rng().fill_bytes(&mut salt);
salt
}
enum SupportedHashSchemes {
Pbkdf2Sha256,
Pbkdf2Sha512,
}
fn from_reference_hash(
hash_info: &PHCEncoded,
) -> Result<Box<Fn(&str) -> Result<String, ErrorCode>>, ErrorCode> {
let algorithm: SupportedHashSchemes = match hash_info.id {
Some(ref scheme_id) => match scheme_id.as_ref() {
"pbkdf2_sha512" => SupportedHashSchemes::Pbkdf2Sha512,
"pbkdf2_sha256" => SupportedHashSchemes::Pbkdf2Sha256,
"pbkdf2" => match hash_info.parameters.get("h") {
Some(h) => match h.as_ref() {
"sha512" => SupportedHashSchemes::Pbkdf2Sha512,
"sha256" => SupportedHashSchemes::Pbkdf2Sha256,
_ => return Err(ErrorCode::InvalidPasswordFormat),
},
None => SupportedHashSchemes::Pbkdf2Sha512,
},
_ => return Err(ErrorCode::InvalidPasswordFormat),
},
None => return Err(ErrorCode::InvalidPasswordFormat),
};
match algorithm {
SupportedHashSchemes::Pbkdf2Sha256 => {
let iterations: NonZeroU32 = {
const DEFAULT_ITERATIONS: u32 = 21000;
let iterations = get_param!(hash_info.parameters, "i", u32, DEFAULT_ITERATIONS);
if iterations == 0 {
NonZeroU32::new(DEFAULT_ITERATIONS).unwrap()
} else {
NonZeroU32::new(iterations).unwrap()
}
};
let output_len: usize = ring::digest::SHA256.output_len;
let salt: Vec<u8> = hash_info.salt().unwrap_or(Vec::new());
Ok(Box::new(move |password: &str| {
let mut out: Vec<u8> = vec![0; output_len];
ring::pbkdf2::derive(
&digest::SHA256,
iterations,
&salt[..],
password.as_bytes(),
&mut out[..],
);
let encoded_salt = to_hex(salt.clone());
let encoded_hash = to_hex(out);
let mut new_hash_info: PHCEncoded = PHCEncoded {
id: Some("pbkdf2_sha256".to_string()),
parameters: HashMap::new(),
parameters_order: Vec::new(),
salt: Some(encoded_salt),
hash: Some(encoded_hash),
};
new_hash_info.insert("i".to_string(), iterations.to_string());
Ok(format!("{}", new_hash_info))
}))
}
SupportedHashSchemes::Pbkdf2Sha512 => {
let iterations: NonZeroU32 = {
const DEFAULT_ITERATIONS: u32 = 21000;
let iterations = get_param!(hash_info.parameters, "i", u32, DEFAULT_ITERATIONS);
if iterations == 0 {
NonZeroU32::new(DEFAULT_ITERATIONS).unwrap()
} else {
NonZeroU32::new(iterations).unwrap()
}
};
let output_len: usize = ring::digest::SHA512.output_len;
let salt: Vec<u8> = hash_info.salt().unwrap_or(Vec::new());
Ok(Box::new(move |password: &str| {
let mut out: Vec<u8> = vec![0; output_len];
ring::pbkdf2::derive(
&digest::SHA512,
iterations,
&salt[..],
password.as_bytes(),
&mut out[..],
);
let encoded_salt = to_hex(salt.clone());
let encoded_hash = to_hex(out);
let mut new_hash_info: PHCEncoded = PHCEncoded {
id: Some("pbkdf2_sha512".to_string()),
parameters: HashMap::new(),
parameters_order: Vec::new(),
salt: Some(encoded_salt),
hash: Some(encoded_hash),
};
new_hash_info.insert("i".to_string(), iterations.to_string());
Ok(format!("{}", new_hash_info))
}))
}
}
}
pub fn derive_password(password: &str) -> Result<String, ErrorCode> {
if password.len() < PASSWORD_MIN_LEN {
return Err(ErrorCode::PasswordTooShort);
} else if password.len() > PASSWORD_MAX_LEN {
return Err(ErrorCode::PasswordTooLong);
}
let iterations: u32 = 21000;
let salt = generate_salt(16);
let encoded_salt = to_hex(salt);
let mut hash_info: PHCEncoded = PHCEncoded {
id: Some("pbkdf2_sha512".to_string()),
parameters: HashMap::new(),
parameters_order: Vec::new(),
salt: Some(encoded_salt),
hash: None,
};
hash_info.insert("i".to_string(), iterations.to_string());
let derive = match from_reference_hash(&hash_info) {
Ok(boxed_func) => boxed_func,
Err(e) => panic!(e),
};
derive(password)
}
pub fn is_valid(password: &str, reference: &str) -> bool {
let hash_info: PHCEncoded = match PHCEncoded::from_string(reference) {
Ok(x) => x,
Err(_) => return false,
};
let algorithm: &'static digest::Algorithm = match hash_info.id {
Some(ref scheme_id) => match scheme_id.as_ref() {
"pbkdf2_sha512" => &digest::SHA512,
"pbkdf2_sha256" => &digest::SHA256,
"pbkdf2" => match hash_info.parameters.get("h") {
Some(h) => match h.as_ref() {
"sha512" => &digest::SHA512,
"sha256" => &digest::SHA256,
_ => return false,
},
None => &digest::SHA512,
},
_ => return false,
},
None => return false,
};
let iterations: NonZeroU32 = {
const DEFAULT_ITERATIONS: u32 = 21000;
let iterations = get_param!(hash_info.parameters, "i", u32, DEFAULT_ITERATIONS);
if iterations == 0 {
NonZeroU32::new(DEFAULT_ITERATIONS).unwrap()
} else {
NonZeroU32::new(iterations).unwrap()
}
};
let salt: Vec<u8> = match hash_info.salt() {
Ok(salt) => salt,
Err(_) => Vec::new(),
};
let password_hash: Vec<u8> = match hash_info.hash() {
Ok(hash) => hash,
Err(_) => return false,
};
match ring::pbkdf2::verify(
algorithm,
iterations,
salt.as_ref(),
password.as_bytes(),
password_hash.as_ref(),
) {
Ok(_) => true,
Err(_) => false,
}
}
#[cfg(feature = "cbindings")]
mod cbindings {
use super::{derive_password, is_valid, ErrorCode};
use libc;
use std;
#[no_mangle]
pub extern "C" fn boringauth_pass_derive_password(
password: *const libc::c_char,
storage: *mut libc::uint8_t,
storage_len: libc::size_t,
) -> ErrorCode {
let mut r_storage = unsafe {
assert!(!storage.is_null());
std::slice::from_raw_parts_mut(storage, storage_len as usize)
};
let c_password = unsafe {
assert!(!password.is_null());
std::ffi::CStr::from_ptr(password)
};
let r_password = c_password.to_str().unwrap();
let r_derived_password = match derive_password(r_password) {
Ok(some) => some,
Err(errno) => return errno,
};
let out_len = r_derived_password.len();
let pass_b = r_derived_password.into_bytes();
if out_len >= storage_len as usize {
return ErrorCode::NotEnoughSpace;
}
for i in 0..out_len {
r_storage[i] = pass_b[i];
}
r_storage[out_len] = 0;
ErrorCode::Success
}
#[no_mangle]
pub extern "C" fn boringauth_pass_is_valid(
password: *const libc::c_char,
reference: *const libc::c_char,
) -> libc::int32_t {
let c_password = unsafe {
assert!(!password.is_null());
std::ffi::CStr::from_ptr(password)
};
let r_password = c_password.to_str().unwrap();
let c_reference = unsafe {
assert!(!reference.is_null());
std::ffi::CStr::from_ptr(reference)
};
let r_reference = c_reference.to_str().unwrap();
is_valid(r_password, r_reference) as libc::int32_t
}
}
#[cfg(feature = "cbindings")]
pub use self::cbindings::boringauth_pass_derive_password;
#[cfg(feature = "cbindings")]
pub use self::cbindings::boringauth_pass_is_valid;
#[cfg(test)]
mod tests {
use super::phc_encoding::PHCEncoded;
use super::{derive_password, from_reference_hash, is_valid};
#[test]
fn test_default_derivation() {
let password = "123456";
let stored_password = derive_password(password).unwrap();
assert!(stored_password.starts_with("$pbkdf2_sha512$"));
}
#[test]
fn test_random_salt() {
let password = "derp";
let stored_password_1 = derive_password(password).unwrap();
let stored_password_2 = derive_password(password).unwrap();
assert!(stored_password_1 != stored_password_2);
}
#[test]
#[should_panic]
fn test_empty_password_deriv() {
derive_password("").unwrap();
}
#[test]
#[should_panic]
fn test_short_password_deriv() {
derive_password("abc").unwrap();
}
#[test]
fn test_empty_password_validation() {
let stored_password = "$pbkdf2$0$45217803$2dade20112c105c0520f8bc08bac2a3be0c241a5";
assert!(!is_valid("", stored_password));
}
#[test]
fn test_short_password_validation() {
let stored_password = "$pbkdf2$0$45217803$ce035bbb80414de3de01dc54a9ee204b27ad1ae5";
assert!(!is_valid("abc", stored_password));
}
#[test]
fn test_utf8_passwords() {
let password_list = [
"è_é ÖÀ",
"пароль",
"密码",
"密碼",
"كلمه السر",
"ль\n\n\n密à\r\n$",
"😁😊😣😺✅✨❕➡🚀🚧Ⓜ🇪🇸⏳🌎",
];
for password in password_list.iter() {
let stored_password = derive_password(password).unwrap();
assert!(!is_valid("bad password", &stored_password));
assert!(is_valid(&password, &stored_password));
}
}
#[test]
fn test_password_with_null_byte() {
let password_list = [
("123456\x00789", "123456"),
("a\x00cd", "a"),
];
for pass in password_list.iter() {
let stored_password = derive_password(pass.0).unwrap();
assert!(!is_valid(pass.1, &stored_password));
assert!(is_valid(pass.0, &stored_password));
}
}
#[test]
fn test_format_with_salt() {
let list = [
("password123",
"$pbkdf2$i=1000$45217803",
"$pbkdf2_sha512$i=1000$45217803$b47d5204bcecf01a31152d0872d03f270d3a8eb2bb305864d098be281bc243b2412f0ed013cc781760e64ddea705cc104c37111d99ebddb36232fe494f24c0ba"),
("password123",
"$pbkdf2$$45217803",
"$pbkdf2_sha512$i=21000$45217803$c538516ce1350cf7d48cc6b59119fa1d94fab9f1b92a2c603c2b78f8fd1800b99d9a4447ddfc1c5c297bdb53cfdf9f736d831854e824af7cadf97a2144b93f6b"),
("password123",
"$pbkdf2$45217803",
"$pbkdf2_sha512$i=21000$45217803$c538516ce1350cf7d48cc6b59119fa1d94fab9f1b92a2c603c2b78f8fd1800b99d9a4447ddfc1c5c297bdb53cfdf9f736d831854e824af7cadf97a2144b93f6b"),
("password123",
"$pbkdf2_sha256$$45217803",
"$pbkdf2_sha256$i=21000$45217803$a607a72c2c92357a4568b998c5f708f801f0b1ffbaea205357e08e4d325830c9"),
("password123",
"$pbkdf2_sha256$45217803",
"$pbkdf2_sha256$i=21000$45217803$a607a72c2c92357a4568b998c5f708f801f0b1ffbaea205357e08e4d325830c9"),
("password123",
"$pbkdf2$h=sha256$45217803",
"$pbkdf2_sha256$i=21000$45217803$a607a72c2c92357a4568b998c5f708f801f0b1ffbaea205357e08e4d325830c9")];
for p in list.iter() {
let derive = match from_reference_hash(&PHCEncoded::from_string(p.1).unwrap()) {
Ok(boxed_func) => boxed_func,
Err(e) => {
println!("derive function could not be determined from reference hash");
panic!(e)
}
};
let hash = derive(p.0).unwrap();
assert_eq!(p.2, hash);
assert!(!is_valid(p.0, p.1));
assert!(is_valid(p.0, p.2));
}
}
#[test]
fn test_format_without_salt() {
let list = [
(
"password123",
"$pbkdf2$i=1000,h=sha256",
"$pbkdf2_sha256$i=1000$",
),
(
"password123",
"$pbkdf2$i=1000,h=sha512",
"$pbkdf2_sha512$i=1000$",
),
("password123", "$pbkdf2_sha512", "$pbkdf2_sha512$i=21000$"),
];
for p in list.iter() {
let derive = match from_reference_hash(&PHCEncoded::from_string(p.1).unwrap()) {
Ok(boxed_func) => boxed_func,
Err(e) => {
println!("derive function could not be determined from reference hash");
panic!(e)
}
};
let hash = derive(p.0).unwrap();
assert!(hash.starts_with(p.2));
}
}
#[test]
fn test_algos() {
let password_list = [
("password123",
"$pbkdf2$i=1000$45217803$b47d5204bcecf01a31152d0872d03f270d3a8eb2bb305864d098be281bc243b2412f0ed013cc781760e64ddea705cc104c37111d99ebddb36232fe494f24c0ba"),
("correct horse battery staple",
"$pbkdf2$i=1000$45217803$53fd7d5cf7bb9ca33f899135642431fe68845faeeb1b673103e9fcef71e537852baadd0d0584fabbaa3e18c699bc084aa707c5a8ff125e515494278471900783"),
("password123",
"$pbkdf2$i=12345$45217803$bcac6e8df12fc3be30f0eb9df3a576cd79ebac5f9ab39b402aa44719cfdb16503e6ca64411681e9f88aa1396c13a927673a9bd991e6252171dc7816fd47db27c"),
("correct horse battery staple",
"$pbkdf2$i=12345$45217803$c3881f03eaff62f42f0edb809a7199078374ddf83f8f3da63897abf190369359ab87ff9c3c4621adbd4451fa7882e0572d3dc625ede84cc1cc834179c67e0866"),
("password123",
"$pbkdf2$i=21000$45217803$c538516ce1350cf7d48cc6b59119fa1d94fab9f1b92a2c603c2b78f8fd1800b99d9a4447ddfc1c5c297bdb53cfdf9f736d831854e824af7cadf97a2144b93f6b"),
("password123",
"$pbkdf2_sha256$i=1000$45217803$c98f36c7e9321230407c7f6785c2a938698709d16d1fb6164c43c83f8b7957b5"),
("correct horse battery staple",
"$pbkdf2_sha256$i=1000$45217803$7c6fe867a7c1924c6ecea1a792773aadb8fb6ccc1d220661f7558a6fa41f15bc"),
("password123",
"$pbkdf2_sha256$i=12345$45217803$995fdcd0cbc0a87bbc1f37915f56ab953cb8843e336b157e4540d9bfbcd0e9b8"),
("correct horse battery staple",
"$pbkdf2_sha256$i=12345$45217803$f42de25f5f2ebea714f73e99ffb02bd1c2747e5939795be263218090f73cc5ce"),
("password123",
"$pbkdf2_sha256$i=21000$45217803$a607a72c2c92357a4568b998c5f708f801f0b1ffbaea205357e08e4d325830c9"),
("correct horse battery staple",
"$pbkdf2_sha256$i=21000$45217803$2aec8d61590dc9e0d128421d345a63eea0923ca5136cdead6bf9fd3f0f8a0447"),
("password123",
"$pbkdf2$i=1000,h=sha256$45217803$c98f36c7e9321230407c7f6785c2a938698709d16d1fb6164c43c83f8b7957b5"),
("correct horse battery staple",
"$pbkdf2$i=1000,h=sha256$45217803$7c6fe867a7c1924c6ecea1a792773aadb8fb6ccc1d220661f7558a6fa41f15bc"),
("password123",
"$pbkdf2$h=sha256,i=12345$45217803$995fdcd0cbc0a87bbc1f37915f56ab953cb8843e336b157e4540d9bfbcd0e9b8"),
("correct horse battery staple",
"$pbkdf2$h=sha256,i=12345$45217803$f42de25f5f2ebea714f73e99ffb02bd1c2747e5939795be263218090f73cc5ce"),
("password123",
"$pbkdf2$i=21000,h=sha256$45217803$a607a72c2c92357a4568b998c5f708f801f0b1ffbaea205357e08e4d325830c9"),
("correct horse battery staple",
"$pbkdf2$i=21000,h=sha256$45217803$2aec8d61590dc9e0d128421d345a63eea0923ca5136cdead6bf9fd3f0f8a0447"),
("password123",
"$pbkdf2_sha512$i=1000$45217803$b47d5204bcecf01a31152d0872d03f270d3a8eb2bb305864d098be281bc243b2412f0ed013cc781760e64ddea705cc104c37111d99ebddb36232fe494f24c0ba"),
("correct horse battery staple",
"$pbkdf2_sha512$i=1000$45217803$53fd7d5cf7bb9ca33f899135642431fe68845faeeb1b673103e9fcef71e537852baadd0d0584fabbaa3e18c699bc084aa707c5a8ff125e515494278471900783"),
("password123",
"$pbkdf2_sha512$i=12345$45217803$bcac6e8df12fc3be30f0eb9df3a576cd79ebac5f9ab39b402aa44719cfdb16503e6ca64411681e9f88aa1396c13a927673a9bd991e6252171dc7816fd47db27c"),
("correct horse battery staple",
"$pbkdf2_sha512$i=12345$45217803$c3881f03eaff62f42f0edb809a7199078374ddf83f8f3da63897abf190369359ab87ff9c3c4621adbd4451fa7882e0572d3dc625ede84cc1cc834179c67e0866"),
("password123",
"$pbkdf2_sha512$i=21000$45217803$c538516ce1350cf7d48cc6b59119fa1d94fab9f1b92a2c603c2b78f8fd1800b99d9a4447ddfc1c5c297bdb53cfdf9f736d831854e824af7cadf97a2144b93f6b"),
("correct horse battery staple",
"$pbkdf2_sha512$i=21000$45217803$2f66e6548c1b43af774db726f6ea40d19aed21ffdb592b2a830b0b01bd59d97da1b7080470f25d1734f1331a71b2216a06296e2e7b826d5b57ba5ae103c6414a"),
("password123",
"$pbkdf2$i=1000,h=sha512$45217803$b47d5204bcecf01a31152d0872d03f270d3a8eb2bb305864d098be281bc243b2412f0ed013cc781760e64ddea705cc104c37111d99ebddb36232fe494f24c0ba"),
("correct horse battery staple",
"$pbkdf2$i=1000,h=sha512$45217803$53fd7d5cf7bb9ca33f899135642431fe68845faeeb1b673103e9fcef71e537852baadd0d0584fabbaa3e18c699bc084aa707c5a8ff125e515494278471900783"),
("password123",
"$pbkdf2$i=12345,h=sha512$45217803$bcac6e8df12fc3be30f0eb9df3a576cd79ebac5f9ab39b402aa44719cfdb16503e6ca64411681e9f88aa1396c13a927673a9bd991e6252171dc7816fd47db27c"),
("correct horse battery staple",
"$pbkdf2$i=12345,h=sha512$45217803$c3881f03eaff62f42f0edb809a7199078374ddf83f8f3da63897abf190369359ab87ff9c3c4621adbd4451fa7882e0572d3dc625ede84cc1cc834179c67e0866"),
("password123",
"$pbkdf2$h=sha512,i=21000$45217803$c538516ce1350cf7d48cc6b59119fa1d94fab9f1b92a2c603c2b78f8fd1800b99d9a4447ddfc1c5c297bdb53cfdf9f736d831854e824af7cadf97a2144b93f6b"),
("correct horse battery staple",
"$pbkdf2$h=sha512,i=21000$45217803$2f66e6548c1b43af774db726f6ea40d19aed21ffdb592b2a830b0b01bd59d97da1b7080470f25d1734f1331a71b2216a06296e2e7b826d5b57ba5ae103c6414a")];
for p in password_list.iter() {
let derive = match from_reference_hash(&PHCEncoded::from_string(p.1).unwrap()) {
Ok(boxed_func) => boxed_func,
Err(e) => panic!(e),
};
let hash = derive(p.0).unwrap();
println!("{}", hash);
println!("{}", p.1);
assert_eq!(
p.1.split("$").collect::<Vec<&str>>().last().unwrap(),
hash.split("$").collect::<Vec<&str>>().last().unwrap()
);
assert!(!is_valid("bad password", p.1));
assert!(is_valid(p.0, p.1));
}
}
}