Created
June 13, 2012 21:36
-
-
Save xrstf/2926619 to your computer and use it in GitHub Desktop.
Wolfram|Alpha password strength calculation in PHP 5.2+
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Wolfram|Alpha password strength calculation | |
* | |
* @see https://gist.github.com/1514997 (original source this class is based on) | |
* @see http://www.wolframalpha.com/input/?i=password+strength+for+god | |
* @see https://github.com/rchouinard/phpass | |
* @license MIT | |
*/ | |
class PasswordStrength { | |
const STRENGTH_VERY_WEAK = 0; | |
const STRENGTH_WEAK = 1; | |
const STRENGTH_FAIR = 2; | |
const STRENGTH_STRONG = 3; | |
const STRENGTH_VERY_STRONG = 4; | |
public function classifyScore($score) { | |
if ($score < 0) return self::STRENGTH_VERY_WEAK; | |
if ($score < 60) return self::STRENGTH_WEAK; | |
if ($score < 70) return self::STRENGTH_FAIR; | |
if ($score < 90) return self::STRENGTH_STRONG; | |
return self::STRENGTH_VERY_STRONG; | |
} | |
public function classify($pw) { | |
return $this->classifyScore($this->calculate($pw)); | |
} | |
/** | |
* Calculate score for a password | |
* | |
* @param string $pw the password to work on | |
* @return int score | |
*/ | |
public function calculate($pw) { | |
$length = strlen($pw); | |
$score = $length * 4; | |
$nUpper = 0; | |
$nLower = 0; | |
$nNum = 0; | |
$nSymbol = 0; | |
$locUpper = array(); | |
$locLower = array(); | |
$locNum = array(); | |
$locSymbol = array(); | |
$charDict = array(); | |
// count character classes | |
for ($i = 0; $i < $length; ++$i) { | |
$ch = $pw[$i]; | |
$code = ord($ch); | |
/* [0-9] */ if ($code >= 48 && $code <= 57) { $nNum++; $locNum[] = $i; } | |
/* [A-Z] */ elseif ($code >= 65 && $code <= 90) { $nUpper++; $locUpper[] = $i; } | |
/* [a-z] */ elseif ($code >= 97 && $code <= 122) { $nLower++; $locLower[] = $i; } | |
/* . */ else { $nSymbol++; $locSymbol[] = $i; } | |
if (!isset($charDict[$ch])) { | |
$charDict[$ch] = 1; | |
} | |
else { | |
$charDict[$ch]++; | |
} | |
} | |
// reward upper/lower characters if pw is not made up of only either one | |
if ($nUpper !== $length && $nLower !== $length) { | |
if ($nUpper !== 0) { | |
$score += ($length - $nUpper) * 2; | |
} | |
if ($nLower !== 0) { | |
$score += ($length - $nLower) * 2; | |
} | |
} | |
// reward numbers if pw is not made up of only numbers | |
if ($nNum !== $length) { | |
$score += $nNum * 4; | |
} | |
// reward symbols | |
$score += $nSymbol * 6; | |
// middle number or symbol | |
foreach (array($locNum, $locSymbol) as $list) { | |
$reward = 0; | |
foreach ($list as $i) { | |
$reward += ($i !== 0 && $i !== $length -1) ? 1 : 0; | |
} | |
$score += $reward * 2; | |
} | |
// chars only | |
if ($nUpper + $nLower === $length) { | |
$score -= $length; | |
} | |
// numbers only | |
if ($nNum === $length) { | |
$score -= $length; | |
} | |
// repeating chars | |
$repeats = 0; | |
foreach ($charDict as $count) { | |
if ($count > 1) { | |
$repeats += $count - 1; | |
} | |
} | |
if ($repeats > 0) { | |
$score -= (int) (floor($repeats / ($length-$repeats)) + 1); | |
} | |
if ($length > 2) { | |
// consecutive letters and numbers | |
foreach (array('/[a-z]{2,}/', '/[A-Z]{2,}/', '/[0-9]{2,}/') as $re) { | |
preg_match_all($re, $pw, $matches, PREG_SET_ORDER); | |
if (!empty($matches)) { | |
foreach ($matches as $match) { | |
$score -= (strlen($match[0]) - 1) * 2; | |
} | |
} | |
} | |
// sequential letters | |
$locLetters = array_merge($locUpper, $locLower); | |
sort($locLetters); | |
foreach ($this->findSequence($locLetters, mb_strtolower($pw)) as $seq) { | |
if (count($seq) > 2) { | |
$score -= (count($seq) - 2) * 2; | |
} | |
} | |
// sequential numbers | |
foreach ($this->findSequence($locNum, mb_strtolower($pw)) as $seq) { | |
if (count($seq) > 2) { | |
$score -= (count($seq) - 2) * 2; | |
} | |
} | |
} | |
return $score; | |
} | |
/** | |
* Find all sequential chars in string $src | |
* | |
* Only chars in $charLocs are considered. $charLocs is a list of numbers. | |
* For example if $charLocs is [0,2,3], then only $src[2:3] is a possible | |
* substring with sequential chars. | |
* | |
* @param array $charLocs | |
* @param string $src | |
* @return array [[c,c,c,c], [a,a,a], ...] | |
*/ | |
private function findSequence($charLocs, $src) { | |
$sequences = array(); | |
$sequence = array(); | |
for ($i = 0; $i < count($charLocs)-1; ++$i) { | |
$here = $charLocs[$i]; | |
$next = $charLocs[$i+1]; | |
$charHere = $src[$charLocs[$i]]; | |
$charNext = $src[$charLocs[$i+1]]; | |
$distance = $next - $here; | |
$charDistance = ord($charNext) - ord($charHere); | |
if ($distance === 1 && $charDistance === 1) { | |
// We find a pair of sequential chars! | |
if (empty($sequence)) { | |
$sequence = array($charHere, $charNext); | |
} | |
else { | |
$sequence[] = $charNext; | |
} | |
} | |
elseif (!empty($sequence)) { | |
$sequences[] = $sequence; | |
$sequence = array(); | |
} | |
} | |
if (!empty($sequence)) { | |
$sequences[] = $sequence; | |
} | |
return $sequences; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A couple corrections:
STRENGTH_VERY_WEAK is scored at
< 50
and STRENGTH_FAIR as< 75