#![cfg_attr(feature = "clippy", feature(plugin))]
#![cfg_attr(feature = "clippy", plugin(clippy))]
#![recursion_limit = "128"]
#![warn(missing_docs)]
#[macro_use]
extern crate derive_builder;
extern crate fancy_regex;
extern crate itertools;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate quick_error;
extern crate regex;
#[cfg(feature = "ser")]
extern crate serde;
#[cfg(feature = "ser")]
#[macro_use]
extern crate serde_derive;
extern crate time;
#[cfg(test)]
#[macro_use]
extern crate quickcheck;
pub use matching::Match;
mod adjacency_graphs;
pub mod feedback;
mod frequency_lists;
pub mod matching;
mod scoring;
pub mod time_estimates;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "ser", derive(Serialize))]
pub struct Entropy {
pub guesses: u64,
pub guesses_log10: u16,
pub crack_times_seconds: time_estimates::CrackTimes,
pub crack_times_display: time_estimates::CrackTimesDisplay,
pub score: u8,
pub feedback: Option<feedback::Feedback>,
pub sequence: Vec<Match>,
pub calc_time: u64,
}
quick_error! {
#[derive(Debug, Clone, Copy)]
pub enum ZxcvbnError {
BlankPassword {
description("Zxcvbn cannot evaluate a blank password")
}
}
}
pub fn zxcvbn(password: &str, user_inputs: &[&str]) -> Result<Entropy, ZxcvbnError> {
if password.is_empty() {
return Err(ZxcvbnError::BlankPassword);
}
let start_time_ns = time::precise_time_ns();
let password = password.chars().take(100).collect::<String>();
let sanitized_inputs = user_inputs
.iter()
.enumerate()
.map(|(i, x)| (x.to_lowercase(), i + 1))
.collect();
let matches = matching::omnimatch(&password, &sanitized_inputs);
let result = scoring::most_guessable_match_sequence(&password, &matches, false);
let calc_time = (time::precise_time_ns() - start_time_ns) / 1_000_000;
let (attack_times, attack_times_display, score) =
time_estimates::estimate_attack_times(result.guesses);
let feedback = feedback::get_feedback(score, &matches);
Ok(Entropy {
guesses: result.guesses,
guesses_log10: result.guesses_log10,
crack_times_seconds: attack_times,
crack_times_display: attack_times_display,
score: score,
feedback: feedback,
sequence: result.sequence,
calc_time: calc_time,
})
}
#[cfg(test)]
mod tests {
use super::*;
use quickcheck::TestResult;
quickcheck! {
fn test_zxcvbn_doesnt_panic(password: String, user_inputs: Vec<String>) -> TestResult {
let inputs = user_inputs.iter().map(|s| s.as_ref()).collect::<Vec<&str>>();
zxcvbn(&password, &inputs).ok();
TestResult::from_bool(true)
}
}
#[test]
fn test_zxcvbn() {
let password = "r0sebudmaelstrom11/20/91aaaa";
let entropy = zxcvbn(password, &[]).unwrap();
assert_eq!(entropy.guesses, 473_471_216_704_000);
assert_eq!(entropy.guesses_log10, 14);
assert_eq!(entropy.score, 4);
assert!(!entropy.sequence.is_empty());
assert!(entropy.feedback.is_none());
}
#[test]
fn test_zxcvbn_unicode() {
let password = "𐰊𐰂𐰄𐰀𐰁";
let entropy = zxcvbn(password, &[]).unwrap();
assert_eq!(entropy.score, 1);
}
#[test]
fn test_zxcvbn_unicode_2() {
let password = "r0sebudmaelstrom丂/20/91aaaa";
let entropy = zxcvbn(password, &[]).unwrap();
assert_eq!(entropy.score, 4);
}
#[test]
fn test_issue_13() {
let password = "Imaginative-Say-Shoulder-Dish-0";
let entropy = zxcvbn(password, &[]).unwrap();
assert_eq!(entropy.score, 4);
}
#[test]
fn test_issue_15_example_1() {
let password = "TestMeNow!";
let entropy = zxcvbn(password, &[]).unwrap();
assert_eq!(entropy.guesses, 372_010_000);
assert_eq!(entropy.guesses_log10, 8);
assert_eq!(entropy.score, 3);
}
#[test]
fn test_issue_15_example_2() {
let password = "hey<123";
let entropy = zxcvbn(password, &[]).unwrap();
assert_eq!(entropy.guesses, 1_010_000);
assert_eq!(entropy.guesses_log10, 6);
assert_eq!(entropy.score, 2);
}
}