Skip to content

Commit

Permalink
Use enum for score
Browse files Browse the repository at this point in the history
  • Loading branch information
Randolf J committed May 11, 2024
1 parent 3b81dce commit 9283861
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 24 deletions.
6 changes: 3 additions & 3 deletions src/feedback.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
//! Contains structs and methods related to generating feedback strings
//! for providing help for the user to generate stronger passwords.

use crate::frequency_lists::DictionaryType;
use crate::matching::patterns::*;
use crate::matching::Match;
use crate::{frequency_lists::DictionaryType, scoring::Score};
use std::fmt;

/// A warning explains what's wrong with the password.
Expand Down Expand Up @@ -153,7 +153,7 @@ impl Feedback {
}
}

pub(crate) fn get_feedback(score: u8, sequence: &[Match]) -> Option<Feedback> {
pub(crate) fn get_feedback(score: Score, sequence: &[Match]) -> Option<Feedback> {
if sequence.is_empty() {
// default feedback
return Some(Feedback {
Expand All @@ -164,7 +164,7 @@ pub(crate) fn get_feedback(score: u8, sequence: &[Match]) -> Option<Feedback> {
],
});
}
if score >= 3 {
if score >= Score::Three {
return None;
}

Expand Down
27 changes: 14 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use std::time::Duration;
#[macro_use]
extern crate quickcheck;

use scoring::Score;
use time_estimates::CrackTimes;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::wasm_bindgen;
Expand Down Expand Up @@ -77,7 +78,7 @@ pub struct Entropy {
crack_times: time_estimates::CrackTimes,
/// Overall strength score from 0-4.
/// Any score less than 3 should be considered too weak.
score: u8,
score: Score,
/// Verbal feedback to help choose better passwords. Set when `score` <= 2.
feedback: Option<feedback::Feedback>,
/// The list of patterns the guess calculation was based on
Expand All @@ -104,7 +105,7 @@ impl Entropy {

/// Overall strength score from 0-4.
/// Any score less than 3 should be considered too weak.
pub fn score(&self) -> u8 {
pub fn score(&self) -> Score {
self.score
}

Expand Down Expand Up @@ -133,8 +134,8 @@ pub fn zxcvbn(password: &str, user_inputs: &[&str]) -> Entropy {
guesses: 0,
guesses_log10: f64::NEG_INFINITY,
crack_times: CrackTimes::new(0),
score: 0,
feedback: feedback::get_feedback(0, &[]),
score: Score::Zero,
feedback: feedback::get_feedback(Score::Zero, &[]),
sequence: Vec::default(),
calc_time: Duration::from_secs(0),
};
Expand Down Expand Up @@ -198,7 +199,7 @@ mod tests {
let password = "r0sebudmaelstrom11/20/91aaaa";
let entropy = zxcvbn(password, &[]);
assert_eq!(entropy.guesses_log10 as u16, 14);
assert_eq!(entropy.score, 4);
assert_eq!(entropy.score, Score::Four);
assert!(!entropy.sequence.is_empty());
assert!(entropy.feedback.is_none());
assert!(entropy.calc_time.as_nanos() > 0);
Expand All @@ -209,7 +210,7 @@ mod tests {
fn test_zxcvbn_empty() {
let password = "";
let entropy = zxcvbn(password, &[]);
assert_eq!(entropy.score, 0);
assert_eq!(entropy.score, Score::Zero);
assert_eq!(entropy.guesses, 0);
assert_eq!(entropy.guesses_log10, f64::NEG_INFINITY);
assert_eq!(entropy.crack_times, CrackTimes::new(0));
Expand All @@ -221,23 +222,23 @@ mod tests {
fn test_zxcvbn_unicode() {
let password = "𐰊𐰂𐰄𐰀𐰁";
let entropy = zxcvbn(password, &[]);
assert_eq!(entropy.score, 1);
assert_eq!(entropy.score, Score::One);
}

#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_zxcvbn_unicode_2() {
let password = "r0sebudmaelstrom丂/20/91aaaa";
let entropy = zxcvbn(password, &[]);
assert_eq!(entropy.score, 4);
assert_eq!(entropy.score, Score::Four);
}

#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_issue_13() {
let password = "Imaginative-Say-Shoulder-Dish-0";
let entropy = zxcvbn(password, &[]);
assert_eq!(entropy.score, 4);
assert_eq!(entropy.score, Score::Four);
}

#[cfg_attr(not(target_arch = "wasm32"), test)]
Expand All @@ -247,7 +248,7 @@ mod tests {
let entropy = zxcvbn(password, &[]);
assert_eq!(entropy.guesses, 372_010_000);
assert!((entropy.guesses_log10 - 8.57055461430783).abs() < f64::EPSILON);
assert_eq!(entropy.score, 3);
assert_eq!(entropy.score, Score::Three);
}

#[cfg_attr(not(target_arch = "wasm32"), test)]
Expand All @@ -257,7 +258,7 @@ mod tests {
let entropy = zxcvbn(password, &[]);
assert_eq!(entropy.guesses, 1_010_000);
assert!((entropy.guesses_log10 - 6.004321373782642).abs() < f64::EPSILON);
assert_eq!(entropy.score, 2);
assert_eq!(entropy.score, Score::Two);
}

#[cfg_attr(not(target_arch = "wasm32"), test)]
Expand All @@ -266,7 +267,7 @@ mod tests {
let password = "!QASW@#EDFR$%TGHY^&UJKI*(OL";
let entropy = zxcvbn(password, &[]);
assert_eq!(entropy.guesses, u64::max_value());
assert_eq!(entropy.score, 4);
assert_eq!(entropy.score, Score::Four);
}

#[cfg_attr(not(target_arch = "wasm32"), test)]
Expand All @@ -275,6 +276,6 @@ mod tests {
let password = "08märz2010";
let entropy = zxcvbn(password, &[]);
assert_eq!(entropy.guesses, 100010000);
assert_eq!(entropy.score, 3);
assert_eq!(entropy.score, Score::Three);
}
}
36 changes: 35 additions & 1 deletion src/scoring.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,41 @@
use crate::matching::patterns::*;
use crate::matching::Match;
use std::cmp;
use std::collections::HashMap;
use std::{cmp, fmt::Display};

#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Score {
/// Can be cracked with 10^3 guesses or less.
Zero = 0,
/// Can be cracked with 10^6 guesses or less.
One,
/// Can be cracked with 10^8 guesses or less.
Two,
/// Can be cracked with 10^10 guesses or less.
Three,
/// Requires more than 10^10 guesses to crack.
Four,
}

impl From<Score> for i8 {
fn from(score: Score) -> i8 {
score as i8
}
}

impl Display for Score {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", i8::from(*self))
}
}

#[cfg(feature = "ser")]
impl serde::Serialize for Score {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
i8::from(*self).serialize(serializer)
}
}

#[derive(Debug, Clone)]
pub struct GuessCalculation {
Expand Down
16 changes: 9 additions & 7 deletions src/time_estimates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

use std::fmt;

use crate::scoring::Score;

/// Back-of-the-envelope crack time estimations, in seconds, based on a few scenarios.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "ser", derive(Serialize))]
Expand Down Expand Up @@ -129,21 +131,21 @@ impl From<CrackTimeSeconds> for std::time::Duration {
}
}

pub(crate) fn estimate_attack_times(guesses: u64) -> (CrackTimes, u8) {
pub(crate) fn estimate_attack_times(guesses: u64) -> (CrackTimes, Score) {
(CrackTimes::new(guesses), calculate_score(guesses))
}

fn calculate_score(guesses: u64) -> u8 {
fn calculate_score(guesses: u64) -> Score {
const DELTA: u64 = 5;
if guesses < 1_000 + DELTA {
0
Score::Zero
} else if guesses < 1_000_000 + DELTA {
1
Score::One
} else if guesses < 100_000_000 + DELTA {
2
Score::Two
} else if guesses < 10_000_000_000 + DELTA {
3
Score::Three
} else {
4
Score::Four
}
}

0 comments on commit 9283861

Please sign in to comment.