1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Security;
9:
10: use Nette;
11:
12:
13: 14: 15:
16: class Passwords
17: {
18: const BCRYPT_COST = 10;
19:
20:
21: 22: 23: 24: 25: 26:
27: public static function hash($password, array $options = NULL)
28: {
29: $cost = isset($options['cost']) ? (int) $options['cost'] : self::BCRYPT_COST;
30: $salt = isset($options['salt']) ? (string) $options['salt'] : Nette\Utils\Random::generate(22, '0-9A-Za-z./');
31:
32: if (PHP_VERSION_ID < 50307) {
33: throw new Nette\NotSupportedException(__METHOD__ . ' requires PHP >= 5.3.7.');
34: } elseif (($len = strlen($salt)) < 22) {
35: throw new Nette\InvalidArgumentException("Salt must be 22 characters long, $len given.");
36: } elseif ($cost < 4 || $cost > 31) {
37: throw new Nette\InvalidArgumentException("Cost must be in range 4-31, $cost given.");
38: }
39:
40: $hash = crypt($password, '$2y$' . ($cost < 10 ? 0 : '') . $cost . '$' . $salt);
41: if (strlen($hash) < 60) {
42: throw new Nette\InvalidStateException('Hash returned by crypt is invalid.');
43: }
44: return $hash;
45: }
46:
47:
48: 49: 50: 51:
52: public static function verify($password, $hash)
53: {
54: return preg_match('#^\$2y\$(?P<cost>\d\d)\$(?P<salt>.{22})#', $hash, $m)
55: && $m['cost'] >= 4 && $m['cost'] <= 31
56: && self::hash($password, $m) === $hash;
57: }
58:
59:
60: 61: 62: 63: 64: 65:
66: public static function needsRehash($hash, array $options = NULL)
67: {
68: $cost = isset($options['cost']) ? (int) $options['cost'] : self::BCRYPT_COST;
69: return !preg_match('#^\$2y\$(?P<cost>\d\d)\$(?P<salt>.{22})#', $hash, $m)
70: || $m['cost'] < $cost;
71: }
72:
73: }
74: