|
12 | 12 | namespace Symfony\Component\Security\Core\Encoder;
|
13 | 13 |
|
14 | 14 | use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;
|
15 |
| -use Symfony\Component\Security\Core\Util\SecureRandomInterface; |
16 | 15 |
|
17 | 16 | /**
|
18 | 17 | * @author Elnur Abdurrakhimov <[email protected]>
|
19 | 18 | * @author Terje Bråten <[email protected]>
|
20 | 19 | */
|
21 | 20 | class BCryptPasswordEncoder extends BasePasswordEncoder
|
22 | 21 | {
|
23 |
| - /** |
24 |
| - * @var SecureRandomInterface |
25 |
| - */ |
26 |
| - private $secureRandom; |
27 |
| - |
28 | 22 | /**
|
29 | 23 | * @var string
|
30 | 24 | */
|
31 | 25 | private $cost;
|
32 | 26 |
|
33 |
| - private static $prefix = null; |
34 |
| - |
35 | 27 | /**
|
36 | 28 | * Constructor.
|
37 | 29 | *
|
38 |
| - * @param SecureRandomInterface $secureRandom A SecureRandomInterface instance |
39 |
| - * @param integer $cost The algorithmic cost that should be used |
| 30 | + * @param integer $cost The algorithmic cost that should be used |
40 | 31 | *
|
41 | 32 | * @throws \InvalidArgumentException if cost is out of range
|
42 | 33 | */
|
43 |
| - public function __construct(SecureRandomInterface $secureRandom, $cost) |
| 34 | + public function __construct($cost) |
44 | 35 | {
|
45 |
| - $this->secureRandom = $secureRandom; |
| 36 | + if (!function_exists('password_hash')) { |
| 37 | + throw new \RuntimeException('To use the BCrypt encoder, you need to upgrade to PHP 5.5 or install the "ircmaxell/password-compat" via Composer.'); |
| 38 | + } |
46 | 39 |
|
47 | 40 | $cost = (int) $cost;
|
48 | 41 | if ($cost < 4 || $cost > 31) {
|
49 | 42 | throw new \InvalidArgumentException('Cost must be in the range of 4-31.');
|
50 | 43 | }
|
51 |
| - $this->cost = sprintf('%02d', $cost); |
52 |
| - |
53 |
| - if (!self::$prefix) { |
54 |
| - self::$prefix = '$'.(version_compare(phpversion(), '5.3.7', '>=') ? '2y' : '2a').'$'; |
55 |
| - } |
56 |
| - } |
57 |
| - |
58 |
| - /** |
59 |
| - * {@inheritdoc} |
60 |
| - */ |
61 |
| - public function encodePassword($raw, $salt) |
62 |
| - { |
63 |
| - if (function_exists('password_hash')) { |
64 |
| - return password_hash($raw, PASSWORD_BCRYPT, array('cost' => $this->cost)); |
65 |
| - } |
66 |
| - |
67 |
| - $salt = self::$prefix.$this->cost.'$'.$this->encodeSalt($this->getRawSalt()); |
68 |
| - $encoded = crypt($raw, $salt); |
69 |
| - if (!is_string($encoded) || strlen($encoded) <= 13) { |
70 |
| - return false; |
71 |
| - } |
72 |
| - |
73 |
| - return $encoded; |
74 |
| - } |
75 |
| - |
76 |
| - /** |
77 |
| - * {@inheritdoc} |
78 |
| - */ |
79 |
| - public function isPasswordValid($encoded, $raw, $salt) |
80 |
| - { |
81 |
| - if (function_exists('password_verify')) { |
82 |
| - return password_verify($raw, $encoded); |
83 |
| - } |
84 | 44 |
|
85 |
| - $crypted = crypt($raw, $encoded); |
86 |
| - if (strlen($crypted) <= 13) { |
87 |
| - return false; |
88 |
| - } |
89 |
| - |
90 |
| - return $this->comparePasswords($encoded, $crypted); |
| 45 | + $this->cost = sprintf('%02d', $cost); |
91 | 46 | }
|
92 | 47 |
|
93 | 48 | /**
|
94 |
| - * Encodes the salt to be used by Bcrypt. |
| 49 | + * Encodes the raw password. |
95 | 50 | *
|
96 |
| - * The blowfish/bcrypt algorithm used by PHP crypt expects a different |
97 |
| - * set and order of characters than the usual base64_encode function. |
98 |
| - * Regular b64: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ |
99 |
| - * Bcrypt b64: ./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 |
100 |
| - * We care because the last character in our encoded string will |
101 |
| - * only represent 2 bits. While two known implementations of |
102 |
| - * bcrypt will happily accept and correct a salt string which |
103 |
| - * has the 4 unused bits set to non-zero, we do not want to take |
104 |
| - * chances and we also do not want to waste an additional byte |
105 |
| - * of entropy. |
| 51 | + * It doesn't work with PHP versions lower than 5.3.7, since |
| 52 | + * the password compat library uses CRYPT_BLOWFISH hash type with |
| 53 | + * the "$2y$" salt prefix (which is not available in the early PHP versions). |
| 54 | + * @see https://github.com/ircmaxell/password_compat/issues/10#issuecomment-11203833 |
106 | 55 | *
|
107 |
| - * @param bytes $random a string of 16 random bytes |
| 56 | + * It is almost best to **not** pass a salt and let PHP generate one for you. |
108 | 57 | *
|
109 |
| - * @return string Properly encoded salt to use with php crypt function |
| 58 | + * @param string $raw The password to encode |
| 59 | + * @param string $salt The salt |
110 | 60 | *
|
111 |
| - * @throws \InvalidArgumentException if string of random bytes is too short |
| 61 | + * @return string The encoded password |
| 62 | + * |
| 63 | + * @link http://lxr.php.net/xref/PHP_5_5/ext/standard/password.c#111 |
112 | 64 | */
|
113 |
| - protected function encodeSalt($random) |
| 65 | + public function encodePassword($raw, $salt) |
114 | 66 | {
|
115 |
| - $len = strlen($random); |
116 |
| - if ($len < 16) { |
117 |
| - throw new \InvalidArgumentException('The bcrypt salt needs 16 random bytes.'); |
118 |
| - } |
119 |
| - if ($len > 16) { |
120 |
| - $random = substr($random, 0, 16); |
121 |
| - } |
| 67 | + $options = array('cost' => $this->cost); |
122 | 68 |
|
123 |
| - $base64raw = str_replace('+', '.', base64_encode($random)); |
124 |
| - $salt128bit = substr($base64raw, 0, 21); |
125 |
| - $lastchar = substr($base64raw, 21, 1); |
126 |
| - $lastchar = strtr($lastchar, 'AQgw', '.Oeu'); |
127 |
| - $salt128bit .= $lastchar; |
| 69 | + if ($salt) { |
| 70 | + $options['salt'] = $salt; |
| 71 | + } |
128 | 72 |
|
129 |
| - return $salt128bit; |
| 73 | + return password_hash($raw, PASSWORD_BCRYPT, $options); |
130 | 74 | }
|
131 | 75 |
|
132 | 76 | /**
|
133 |
| - * @return bytes 16 random bytes to be used in the salt |
| 77 | + * {@inheritdoc} |
134 | 78 | */
|
135 |
| - protected function getRawSalt() |
| 79 | + public function isPasswordValid($encoded, $raw, $salt) |
136 | 80 | {
|
137 |
| - $rawSalt = false; |
138 |
| - $numBytes = 16; |
139 |
| - if (function_exists('mcrypt_create_iv')) { |
140 |
| - $rawSalt = mcrypt_create_iv($numBytes, MCRYPT_DEV_URANDOM); |
141 |
| - } |
142 |
| - if (!$rawSalt) { |
143 |
| - $rawSalt = $this->secureRandom->nextBytes($numBytes); |
144 |
| - } |
145 |
| - |
146 |
| - return $rawSalt; |
| 81 | + return password_verify($raw, $encoded); |
147 | 82 | }
|
148 | 83 | }
|
0 commit comments