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