class CryptHelper { /** * 加密 * @param unknown $password * @param unknown $salt * @return string */ public static function crypt($password,$salt){ // $saltPrefix .= '$2y$'; // Blowfish 算法 // $saltPrefix .= '13'; // 兩位 cost 參數 // $saltPrefix .= '$'; // 一個 $ // $saltSuffix .= 'LGsF2ctmKKHE1yr2Py.vtu'; return crypt($password, $salt); } /** * 字符比較 * @param unknown $expected * @param unknown $actual * @return boolean */ public static function compareString($expected, $actual) { $expected .= "\0"; $actual .= "\0"; $expectedLength = mb_strlen($expected, '8bit'); $actualLength = mb_strlen($actual, '8bit'); $diff = $expectedLength - $actualLength; for ($i = 0; $i < $actualLength; $i++) { $diff |= (ord($actual[$i]) ^ ord($expected[$i % $expectedLength])); } return $diff === 0; } /** * 校驗密碼 * @param unknown $password * @param unknown $hash * @return boolean */ public static function validatePassword($password, $hash) { if (!is_string($password) || $password === '') { return false; } if (!preg_match('/^\$2[axy]\$(\d\d)\$[\.\/0-9A-Za-z]{22}/', $hash, $matches) || $matches[1] < 4 || $matches[1] > 30 ) { return false; } $test = self::crypt($password, $hash); $n = strlen($test); if ($n !== 60) { return false; } return self::compareString($test, $hash); } }
測試
// --------- 測試 -------- // 加密 $salt = '$2y$13$LGsF2ctmKKHE1yr2Py.vtu'; // 7 + 22 == 29 $password = 'Aa123456'; echo CryptHelper::crypt($password,$salt); echo PHP_EOL; // 校驗 $hash = '$2y$13$LGsF2ctmKKHE1yr2Py.vtuiUR/A0C6tARkCxMO.LUlsiRISu7u53m'; echo CryptHelper::crypt($password,$hash); echo PHP_EOL; echo strlen($hash); echo PHP_EOL; echo CryptHelper::validatePassword($password, $hash);