encryptY($password, $salt); if (!$encryptedPassword) { $encryptedPassword = $this->encryptA($password, $salt); } if (!$encryptedPassword) { $encryptedPassword = $this->encryptX($password, $salt); } // We *never* want to generate blank passwords. If something // goes wrong, throw an exception. if (strpos($encryptedPassword ?? '', '$2') === false) { throw new PasswordEncryptor_EncryptionFailed('Blowfish password encryption failed.'); } return $encryptedPassword; } public function encryptX($password, $salt) { $methodAndSalt = '$2x$' . $salt; $encryptedPassword = crypt($password ?? '', $methodAndSalt ?? ''); if (strpos($encryptedPassword ?? '', '$2x$') === 0) { return $encryptedPassword; } // Check if system a is actually x, and if available, use that. if ($this->checkAEncryptionLevel() == 'x') { $methodAndSalt = '$2a$' . $salt; $encryptedPassword = crypt($password ?? '', $methodAndSalt ?? ''); if (strpos($encryptedPassword ?? '', '$2a$') === 0) { $encryptedPassword = '$2x$' . substr($encryptedPassword ?? '', strlen('$2a$')); return $encryptedPassword; } } return false; } public function encryptY($password, $salt) { $methodAndSalt = '$2y$' . $salt; $encryptedPassword = crypt($password ?? '', $methodAndSalt ?? ''); if (strpos($encryptedPassword ?? '', '$2y$') === 0) { return $encryptedPassword; } // Check if system a is actually y, and if available, use that. if ($this->checkAEncryptionLevel() == 'y') { $methodAndSalt = '$2a$' . $salt; $encryptedPassword = crypt($password ?? '', $methodAndSalt ?? ''); if (strpos($encryptedPassword ?? '', '$2a$') === 0) { $encryptedPassword = '$2y$' . substr($encryptedPassword ?? '', strlen('$2a$')); return $encryptedPassword; } } return false; } public function encryptA($password, $salt) { if ($this->checkAEncryptionLevel() == 'a') { $methodAndSalt = '$2a$' . $salt; $encryptedPassword = crypt($password ?? '', $methodAndSalt ?? ''); if (strpos($encryptedPassword ?? '', '$2a$') === 0) { return $encryptedPassword; } } return false; } /** * The algorithm returned by using '$2a$' is not consistent - * it might be either the correct (y), incorrect (x) or mostly-correct (a) * version, depending on the version of PHP and the operating system, * so we need to test it. */ public function checkAEncryptionLevel() { // Test hashes taken from // http://cvsweb.openwall.com/cgi/cvsweb.cgi/~checkout~/Owl/packages/glibc // /crypt_blowfish/wrapper.c?rev=1.9.2.1;content-type=text%2Fplain $xOrY = crypt("\xff\xa334\xff\xff\xff\xa3345", '$2a$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi') == '$2a$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi'; $yOrA = crypt("\xa3", '$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq') == '$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq'; if ($xOrY && $yOrA) { return 'y'; } elseif ($xOrY) { return 'x'; } elseif ($yOrA) { return 'a'; } return 'unknown'; } /** * self::$cost param is forced to be two digits with leading zeroes for ints 4-9 * * @param string $password * @param Member $member * @return string */ public function salt($password, $member = null) { $generator = new RandomGenerator(); return sprintf('%02d', self::$cost) . '$' . substr($generator->randomToken('sha1') ?? '', 0, 22); } public function check($hash, $password, $salt = null, $member = null) { if (strpos($hash ?? '', '$2y$') === 0) { return $hash === $this->encryptY($password, $salt); } elseif (strpos($hash ?? '', '$2a$') === 0) { return $hash === $this->encryptA($password, $salt); } elseif (strpos($hash ?? '', '$2x$') === 0) { return $hash === $this->encryptX($password, $salt); } return false; } }