diff options
author | Laria Carolin Chabowski <laria@laria.me> | 2020-09-25 23:09:31 +0200 |
---|---|---|
committer | Laria Carolin Chabowski <laria@laria.me> | 2020-09-25 23:09:31 +0200 |
commit | 5e347e4efaa81c2108256dc927208cd55dc10baa (patch) | |
tree | fccd9fea24e436ea2f3fa073475b2b665a4c31cf /ratatoeskr/sys/PasswordHash.php | |
parent | 854fef47192b45517d112e630fe2afa830432253 (diff) | |
download | ratatoeskr-cms-5e347e4efaa81c2108256dc927208cd55dc10baa.tar.gz ratatoeskr-cms-5e347e4efaa81c2108256dc927208cd55dc10baa.tar.bz2 ratatoeskr-cms-5e347e4efaa81c2108256dc927208cd55dc10baa.zip |
Use password_hash() and friends to hash and verify passwords
Previously I rolled my own password hashing function. While it at least
used some sort of salt, it's still a terrible idea.
The newly created class PasswordHash wraps the password_hash() family of
functions but can also check the old password hash format (to distinguish
them, the new password hashes are prefixed with a '!'). In
PasswordHash::needsRehash we then always report an hash of the old format
as being in need of a rehash. That way, these old hashes will be replaced
the next time the user successfully logs in.
Diffstat (limited to 'ratatoeskr/sys/PasswordHash.php')
-rw-r--r-- | ratatoeskr/sys/PasswordHash.php | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/ratatoeskr/sys/PasswordHash.php b/ratatoeskr/sys/PasswordHash.php new file mode 100644 index 0000000..9228642 --- /dev/null +++ b/ratatoeskr/sys/PasswordHash.php @@ -0,0 +1,91 @@ +<?php + + +namespace r7r\cms\sys; + +/** + * Functions for creating and checking password hashes. Mostly wrappers around php's builtin password_\* functions but + * can also verify our old legacy password hash. + */ +class PasswordHash +{ + private const PASSWORD_ALGO = \PASSWORD_DEFAULT; + + /** @var bool */ + private $isLegacy; + + /** @var string */ + private $hashData; + + private function __construct(bool $isLegacy, string $hashData) + { + $this->isLegacy = $isLegacy; + $this->hashData = $hashData; + } + + private static function verifyLegacy(string $password, string $pwhash): bool + { + list($iterations, $hexsalt) = explode('$', $pwhash); + return self::hashLegacy($password, pack("H*", $hexsalt), $iterations) == $pwhash; + } + + private static function hashLegacy(string $data, $salt, string $iterations): string + { + $hash = $data . $salt; + for ($i = $iterations ;$i--;) { + $hash = sha1($data . $hash . $salt, (bool) $i); + } + return $iterations . '$' . bin2hex($salt) . '$' . $hash; + } + + private function format(): string + { + return $this->isLegacy + ? $this->hashData + : '!' . $this->hashData; + } + + private static function parse(string $s): self + { + return substr($s, 0, 1) === '!' + ? new self(false, substr($s, 1)) + : new self(true, $s); + } + + /** + * Verifies that a given password is valid for the given hash + * @param string $password + * @param string $hash + * @return bool + */ + public static function verify(string $password, string $hash): bool + { + $hash = self::parse($hash); + return $hash->isLegacy + ? self::verifyLegacy($password, $hash->hashData) + : password_verify($password, $hash->hashData); + } + + /** + * Creates a hash for a password + * @param string $password + * @return string Treat this as opaque data. Don't rely on it being in a certain format, it might change in the future. + */ + public static function hash(string $password): string + { + return (new self(false, password_hash($password, self::PASSWORD_ALGO)))->format(); + } + + /** + * Checks, if a given hash should be recomputed (because it's not considered secure any more) if the password is known. + * @param string $hash + * @return bool + */ + public static function needsRehash(string $hash): bool + { + $hash = self::parse($hash); + return $hash->isLegacy + ? true + : password_needs_rehash($hash->hashData, self::PASSWORD_ALGO); + } +} |