#![warn(missing_docs)]
mod enc_dec;
pub mod error;
pub mod unix_crypt;
pub mod bsdi_crypt;
mod des_crypt;
pub mod bcrypt;
pub mod sha1_crypt;
pub mod md5_crypt;
mod sha2_crypt;
pub mod sha256_crypt;
pub mod sha512_crypt;
pub type Result<T> = std::result::Result<T, error::Error>;
pub struct HashSetup<'a> {
pub salt: Option<&'a str>,
pub rounds: Option<u32>,
}
pub trait IntoHashSetup<'a> {
fn into_hash_setup(self, f: fn(&'a str) -> Result<HashSetup<'a>>) -> Result<HashSetup<'a>>;
}
impl<'a> IntoHashSetup<'a> for &'a str {
fn into_hash_setup(self, f: fn(&'a str) -> Result<HashSetup<'a>>) -> Result<HashSetup<'a>> {
f(self)
}
}
impl<'a> IntoHashSetup<'a> for HashSetup<'a> {
fn into_hash_setup(self, _f: fn(&'a str) -> Result<HashSetup<'a>>) -> Result<HashSetup<'a>> {
Ok(self)
}
}
pub trait FindNul {
fn nul_terminated_subslice(&self) -> &[u8];
}
impl FindNul for str {
fn nul_terminated_subslice(&self) -> &[u8] {
let nul_pos = self.as_bytes().windows(1).position(|window| window == [0u8]).unwrap_or_else(|| self.len());
self[..nul_pos].as_bytes()
}
}
impl FindNul for [u8] {
fn nul_terminated_subslice(&self) -> &[u8] {
let nul_pos = self.windows(1).position(|window| window == [0u8]).unwrap_or_else(|| self.len());
self[..nul_pos].as_ref()
}
}
fn consteq(hash: &str, calchash: Result<String>) -> bool {
if calchash.is_err() {
return false;
}
let hstr = calchash.unwrap();
if hash.len() != hstr.len() {
return false;
}
0 == hash.bytes().zip(hstr.bytes()).fold(0, |xs, (h1, h2)| xs | h1 ^ h2)
}
mod random {
use rand::{Rng, random};
use rand::rngs::OsRng;
use rand::distributions::Standard;
use crate::enc_dec::bcrypt_hash64_encode;
pub fn gen_salt_str(chars: usize) -> String {
let bytes = ((chars + 3) / 4) * 3;
let rv = OsRng.sample_iter(&Standard).take(bytes).collect::<Vec<u8>>();
let mut sstr = bcrypt_hash64_encode(&rv);
while sstr.len() > chars {
sstr.pop();
}
sstr
}
pub fn gen_salt_bytes(bytes: &mut [u8]) {
OsRng.fill(bytes);
}
pub fn vary_rounds(ceil: u32) -> u32 {
ceil - (random::<u32>() % (ceil / 4))
}
}
mod parse {
use std::str;
pub trait HashIterator {
type Elem;
fn take(&mut self, n: usize) -> Option<Self::Elem>;
fn take_until(&mut self, ac: u8) -> Option<Self::Elem>;
fn at_end(&mut self) -> bool;
}
pub struct HashSlice<'a> {
bp: &'a [u8],
len: usize,
pos: usize,
}
impl<'a> HashSlice<'a> {
pub fn new(hash: &'a str) -> HashSlice<'a> {
HashSlice { bp: hash.as_bytes(), len: hash.len(), pos: 0 }
}
}
impl<'a> HashIterator for HashSlice<'a> {
type Elem = &'a str;
fn take(&mut self, n: usize) -> Option<Self::Elem> {
if self.pos > self.len {
return None;
}
let sp = self.pos;
if sp + n > self.len {
self.pos = self.len + 1;
None
} else {
let endp = self.pos + n;
self.pos = endp + if endp == self.len { 1 } else { 0 };
if let Ok(s) = str::from_utf8(&self.bp[sp..endp]) {
Some(s)
} else {
None
}
}
}
fn take_until(&mut self, ac: u8) -> Option<Self::Elem> {
if self.pos > self.len {
return None;
}
let mut sp = self.pos;
while sp < self.len {
if self.bp[sp] == ac {
break;
}
sp += 1;
}
let oldp = self.pos;
self.pos = sp + 1;
if let Ok(s) = str::from_utf8(&self.bp[oldp..sp]) {
Some(s)
} else {
None
}
}
fn at_end(&mut self) -> bool {
self.take(0).unwrap_or("X") == "X"
}
}
#[cfg(test)]
mod tests {
use super::{HashSlice, HashIterator};
#[test]
fn drain_string() {
let mut hs = HashSlice::new("$2y$05$bvIG6Nmid91Mu9RcmmWZfO5HJIMCT8riNW0hEp8f6/FuA2/mHZFpe");
assert_eq!(hs.take_until(b'$').unwrap(), "");
assert_eq!(hs.take_until(b'$').unwrap(), "2y");
assert_eq!(hs.take_until(b'$').unwrap(), "05");
assert_eq!(hs.take(22).unwrap(), "bvIG6Nmid91Mu9RcmmWZfO");
let mut hs1 = HashSlice { bp: hs.bp, pos: hs.pos, len: hs.len };
assert_eq!(hs.take_until(b'$').unwrap(), "5HJIMCT8riNW0hEp8f6/FuA2/mHZFpe");
assert_eq!(hs.at_end(), true);
assert_eq!(hs1.take(31).unwrap(), "5HJIMCT8riNW0hEp8f6/FuA2/mHZFpe");
assert_eq!(hs1.at_end(), true);
}
#[test]
fn empty_string() {
let mut hs = HashSlice::new("");
assert_eq!(hs.take_until(b'$').unwrap(), "");
assert_eq!(hs.at_end(), true);
let mut hs = HashSlice::new("");
assert_eq!(hs.at_end(), false);
}
#[test]
fn empty_elements() {
let mut hs = HashSlice::new("$");
assert_eq!(hs.take_until(b'$').unwrap(), "");
assert_eq!(hs.take_until(b'$').unwrap(), "");
assert_eq!(hs.at_end(), true);
}
#[test]
fn combined_take() {
let mut hs = HashSlice::new("$");
let _ = hs.take_until(b'$').unwrap();
assert_eq!(hs.take_until(b'$').unwrap(), "");
assert_eq!(hs.at_end(), true);
}
}
}
pub mod unix {
use super::{Result, consteq};
use crate::parse::{self, HashIterator};
use crate::error::Error;
use crate::{bsdi_crypt, md5_crypt, bcrypt, sha1_crypt, sha256_crypt, sha512_crypt, unix_crypt};
pub fn crypt<B: AsRef<[u8]>>(pass: B, hash: &str) -> Result<String> {
let mut hs = parse::HashSlice::new(hash);
#[allow(deprecated)]
match hs.take(1).unwrap_or("X") {
"_" => bsdi_crypt::hash_with(hash, pass),
"$" => match hs.take_until(b'$').unwrap_or("X") {
"1" => md5_crypt::hash_with(hash, pass),
"2a" | "2b" | "2y" => bcrypt::hash_with(hash, pass),
"sha1" => sha1_crypt::hash_with(hash, pass),
"5" => sha256_crypt::hash_with(hash, pass),
"6" => sha512_crypt::hash_with(hash, pass),
_ => Err(Error::InvalidHashString),
},
_ => unix_crypt::hash_with(hash, pass)
}
}
pub fn verify<B: AsRef<[u8]>>(pass: B, hash: &str) -> bool {
consteq(hash, crypt(pass, hash))
}
#[cfg(test)]
mod tests {
#[test]
fn crypt_recognized() {
assert_eq!(super::crypt("password", "$1$5pZSV9va$azfrPr6af3Fc7dLblQXVa0").unwrap(),
"$1$5pZSV9va$azfrPr6af3Fc7dLblQXVa0");
assert_eq!(super::crypt("test", "aZGJuE6EXrjEE").unwrap(), "aZGJuE6EXrjEE");
}
}
}