= 10^10) */ public $score; /** * @var object verbal feedback to help choose better passwords. set when score <= 2. { * @type string $warning explains what's wrong, eg. 'this is a top-10 common password'. not always set -- sometimes an empty string * @type array $suggestions a possibly-empty list of suggestions to help choose a less guessable password. eg. 'Add another word or two' * } */ public $feedback; /** * @var array the list of patterns that zxcvbn based the guess calculation on. */ public $sequence; /** * @var int how long it took zxcvbn to calculate an answer, in milliseconds */ public $calc_time; public function __construct( $password, $guesses, $optimal_match_sequence ) { $this->password = $password; $this->guesses = $guesses; $this->guesses_log10 = log10( $guesses ); $this->sequence = $optimal_match_sequence; $this->estimate_attack_times(); $this->calc_score(); $this->feedback(); } protected function estimate_attack_times() { $this->crack_times_seconds = array( 'online_throttling_100_per_hour' => $this->guesses / ( 100 / 3600 ), 'online_no_throttling_10_per_second' => $this->guesses / 10, 'offline_slow_hashing_1e4_per_second' => $this->guesses / 1e4, 'offline_fast_hashing_1e10_per_second' => $this->guesses / 1e10 ); foreach ( $this->crack_times_seconds as $key => $seconds ) { $this->crack_times_display[ $key ] = $this->display_time( $seconds ); } } protected function calc_score() { $delta = 5; if ( $this->guesses < 1e3 + $delta ) { $this->score = 0; } elseif ( $this->guesses < 1e6 + $delta ) { $this->score = 1; } elseif ( $this->guesses < 1e8 + $delta ) { $this->score = 2; } elseif ( $this->guesses < 1e10 + $delta ) { $this->score = 3; } else { $this->score = 4; } } protected function display_time( $seconds ) { $minute = 60; $hour = $minute * 60; $day = $hour * 24; $month = $day * 31; $year = $month * 12; $century = $year * 100; if ( $seconds < 1 ) { return 'less than a second'; } elseif( $seconds < $minute ) { return sprintf( '%d second%s', $seconds, ( $seconds > 1 )? 's':'' ); } elseif( $seconds < $hour ) { $base = round( $seconds / $minute ); return sprintf( '%d minute%s', $base, ( $base > 1 )? 's':'' ); } elseif( $seconds < $day ) { $base = round( $seconds / $hour ); return sprintf( '%d hour%s', $base, ( $base > 1 )? 's':'' ); } elseif( $seconds < $month ) { $base = round( $seconds / $day ); return sprintf( '%d day%s', $base, ( $base > 1 )? 's':'' ); } elseif( $seconds < $year ) { $base = round( $seconds / $month ); return sprintf( '%d month%s', $base, ( $base > 1 )? 's':'' ); } elseif( $seconds < $century ) { $base = round( $seconds / $year ); return sprintf( '%d year%s', $base, ( $base > 1 )? 's':'' ); } else { return 'centuries'; } } protected function feedback() { $this->feedback = new stdClass(); $this->feedback->warning = ''; $this->feedback->suggestions = array(); if ( 0 === count( $this->sequence ) ) { $this->feedback->suggestions = array( 'Use a few words, avoid common phrases', 'No need for symbols, digits, or uppercase letters' ); return; } if ( $this->score > 2 ) { return; } // tie feedback to the longest match for longer sequences $longest_match = $this->sequence[0]; foreach ( $this->sequence as $m ) { if ( strlen( $m->token ) > strlen( $longest_match->token ) ) { $longest_match = $m; } } $is_sole_match = count( $this->sequence ) == 1; if ( is_object( $longest_match ) && is_callable( array( $longest_match, 'get_feedback' ) ) ) { $this->feedback = $longest_match->get_feedback( $is_sole_match ); } } }