function minimum_entropy_match_sequence($password, $matches) { # e.g. 26 for lowercase $bruteforce_cardinality = calc_bruteforce_cardinality($password); # minimum entropy up to k. $up_to_k = array(); # for the optimal sequence of matches up to k, holds the # final match (match.j == k). null means the sequence ends # w/ a brute-force character. $backpointers = array(); $password_len = strlen($password); for ($k = 0; $k < $password_len; $k++) { # starting scenario to try and beat: # adding a brute-force character to the minimum entropy sequence at k-1. $up_to_k[$k] = _get_index($up_to_k, $k - 1) + logarithm($bruteforce_cardinality); $backpointers[$k] = NULL; foreach ($matches as $match) { if ($match['j'] !== $k) { continue; } $i = $match['i']; $j = $match['j']; # see if best entropy up to i-1 + entropy of this match # is less than the current minimum at j. $candidate_entropy = _get_index($up_to_k, $i - 1) + calc_entropy($match); if ($candidate_entropy < $up_to_k[$j]) { $up_to_k[$j] = $candidate_entropy; $backpointers[$j] = $match; } } } # walk backwards and decode the best sequence $match_sequence = array(); $k = strlen($password) - 1; while ($k >= 0) { $match = $backpointers[$k]; if ($match) { $match_sequence[] = $match; $k = $match['i'] - 1; } else { $k -= 1; } } $match_sequence = array_reverse($match_sequence); # fill in the blanks between pattern matches with bruteforce "matches" # that way the match sequence fully covers the password: # match1.j == match2.i - 1 for every adjacent match1, match2. $make_bruteforce_match = function ($i, $j) use($password, $bruteforce_cardinality) { return array('pattern' => 'bruteforce', 'i' => $i, 'j' => $j, 'token' => _slice($password, $i, $j + 1), 'entropy' => logarithm(pow($bruteforce_cardinality, $j - $i + 1)), 'cardinality' => $bruteforce_cardinality); }; $k = 0; $match_sequence_copy = array(); foreach ($match_sequence as $match) { $i = $match['i']; $j = $match['j']; if ($i - $k > 0) { $match_sequence_copy[] = $make_bruteforce_match($k, $i - 1); } $k = $j + 1; $match_sequence_copy[] = $match; } if ($k < strlen($password)) { $match_sequence_copy[] = $make_bruteforce_match($k, strlen($password) - 1); } $match_sequence = $match_sequence_copy; # or 0 corner case is for an empty password '' if (isset($up_to_k[strlen($password) - 1])) { $min_entropy = $up_to_k[strlen($password) - 1]; } else { $min_entropy = 0; } $crack_time = entropy_to_crack_time($min_entropy); # final result object $result = array('password' => $password, 'entropy' => round_to_x_digits($min_entropy, 3), 'match_sequence' => $match_sequence, 'crack_time_seconds' => round_to_x_digits($crack_time, 3), 'crack_time_display' => display_time($crack_time), 'score' => crack_time_to_score($crack_time)); return $result; }
function date_sep_match($password) { global $date_rx_year_suffix, $date_rx_year_prefix; $matches = array(); foreach (findall($password, $date_rx_year_suffix) as $match) { $match['day'] = (int) $match[1]; $match['month'] = (int) $match[3]; $match['year'] = (int) $match[4]; $match['sep'] = $match[2]; $matches[] = $match; } foreach (findall($password, $date_rx_year_prefix) as $match) { $match['day'] = (int) $match[4]; $match['month'] = (int) $match[3]; $match['year'] = (int) $match[1]; $match['sep'] = $match[2]; $matches[] = $match; } $results = array(); foreach ($matches as $match) { list($valid, $date) = check_date($match['day'], $match['month'], $match['year']); if (!$valid) { continue; } list($day, $month, $year) = $date; $i = $match['i']; $j = $match['j']; $results[] = array('pattern' => 'date', 'i' => $i, 'j' => $j, 'token' => _slice($password, $i, $j + 1), 'separator' => $match['sep'], 'day' => $day, 'month' => $month, 'year' => $year); } return $results; }