18cdbb23605f7abfb1ba2a7f588db60d59ed8354
[Packages/TYPO3.CMS.git] / typo3 / sysext / saltedpasswords / Classes / Salt / BlowfishSalt.php
1 <?php
2 namespace TYPO3\CMS\Saltedpasswords\Salt;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 /**
18 * Class that implements Blowfish salted hashing based on PHP's
19 * crypt() function.
20 *
21 * Warning: Blowfish salted hashing with PHP's crypt() is not available
22 * on every system.
23 */
24 class BlowfishSalt extends Md5Salt
25 {
26 /**
27 * The default log2 number of iterations for password stretching.
28 */
29 const HASH_COUNT = 7;
30
31 /**
32 * The default maximum allowed log2 number of iterations for
33 * password stretching.
34 */
35 const MAX_HASH_COUNT = 17;
36
37 /**
38 * The default minimum allowed log2 number of iterations for
39 * password stretching.
40 */
41 const MIN_HASH_COUNT = 4;
42
43 /**
44 * Keeps log2 number
45 * of iterations for password stretching.
46 *
47 * @var int
48 */
49 protected static $hashCount;
50
51 /**
52 * Keeps maximum allowed log2 number
53 * of iterations for password stretching.
54 *
55 * @var int
56 */
57 protected static $maxHashCount;
58
59 /**
60 * Keeps minimum allowed log2 number
61 * of iterations for password stretching.
62 *
63 * @var int
64 */
65 protected static $minHashCount;
66
67 /**
68 * Keeps length of a Blowfish salt in bytes.
69 *
70 * @var int
71 */
72 protected static $saltLengthBlowfish = 16;
73
74 /**
75 * Setting string to indicate type of hashing method (blowfish).
76 *
77 * @var string
78 */
79 protected static $settingBlowfish = '$2a$';
80
81 /**
82 * Method applies settings (prefix, hash count) to a salt.
83 *
84 * Overwrites {@link Md5Salt::applySettingsToSalt()}
85 * with Blowfish specifics.
86 *
87 * @param string $salt A salt to apply setting to
88 * @return string Salt with setting
89 */
90 protected function applySettingsToSalt($salt)
91 {
92 $saltWithSettings = $salt;
93 $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
94 // salt without setting
95 if (strlen($salt) == $reqLenBase64) {
96 $saltWithSettings = $this->getSetting() . sprintf('%02u', $this->getHashCount()) . '$' . $salt;
97 }
98 return $saltWithSettings;
99 }
100
101 /**
102 * Parses the log2 iteration count from a stored hash or setting string.
103 *
104 * @param string $setting Complete hash or a hash's setting string or to get log2 iteration count from
105 * @return int Used hashcount for given hash string
106 */
107 protected function getCountLog2($setting)
108 {
109 $countLog2 = null;
110 $setting = substr($setting, strlen($this->getSetting()));
111 $firstSplitPos = strpos($setting, '$');
112 // Hashcount existing
113 if ($firstSplitPos !== false && $firstSplitPos <= 2 && is_numeric(substr($setting, 0, $firstSplitPos))) {
114 $countLog2 = (int)substr($setting, 0, $firstSplitPos);
115 }
116 return $countLog2;
117 }
118
119 /**
120 * Method returns log2 number of iterations for password stretching.
121 *
122 * @return int log2 number of iterations for password stretching
123 * @see HASH_COUNT
124 * @see $hashCount
125 * @see setHashCount()
126 */
127 public function getHashCount()
128 {
129 return isset(self::$hashCount) ? self::$hashCount : self::HASH_COUNT;
130 }
131
132 /**
133 * Method returns maximum allowed log2 number of iterations for password stretching.
134 *
135 * @return int Maximum allowed log2 number of iterations for password stretching
136 * @see MAX_HASH_COUNT
137 * @see $maxHashCount
138 * @see setMaxHashCount()
139 */
140 public function getMaxHashCount()
141 {
142 return isset(self::$maxHashCount) ? self::$maxHashCount : self::MAX_HASH_COUNT;
143 }
144
145 /**
146 * Returns whether all prerequisites for the hashing methods are matched
147 *
148 * @return bool Method available
149 */
150 public function isAvailable()
151 {
152 return CRYPT_BLOWFISH;
153 }
154
155 /**
156 * Method returns minimum allowed log2 number of iterations for password stretching.
157 *
158 * @return int Minimum allowed log2 number of iterations for password stretching
159 * @see MIN_HASH_COUNT
160 * @see $minHashCount
161 * @see setMinHashCount()
162 */
163 public function getMinHashCount()
164 {
165 return isset(self::$minHashCount) ? self::$minHashCount : self::MIN_HASH_COUNT;
166 }
167
168 /**
169 * Returns length of a Blowfish salt in bytes.
170 *
171 * Overwrites {@link Md5Salt::getSaltLength()}
172 * with Blowfish specifics.
173 *
174 * @return int Length of a Blowfish salt in bytes
175 */
176 public function getSaltLength()
177 {
178 return self::$saltLengthBlowfish;
179 }
180
181 /**
182 * Returns setting string of Blowfish salted hashes.
183 *
184 * Overwrites {@link Md5Salt::getSetting()}
185 * with Blowfish specifics.
186 *
187 * @return string Setting string of Blowfish salted hashes
188 */
189 public function getSetting()
190 {
191 return self::$settingBlowfish;
192 }
193
194 /**
195 * Checks whether a user's hashed password needs to be replaced with a new hash.
196 *
197 * This is typically called during the login process when the plain text
198 * password is available. A new hash is needed when the desired iteration
199 * count has changed through a change in the variable $hashCount or
200 * HASH_COUNT.
201 *
202 * @param string $saltedPW Salted hash to check if it needs an update
203 * @return bool TRUE if salted hash needs an update, otherwise FALSE
204 */
205 public function isHashUpdateNeeded($saltedPW)
206 {
207 // Check whether this was an updated password.
208 if (strncmp($saltedPW, '$2', 2) || !$this->isValidSalt($saltedPW)) {
209 return true;
210 }
211 // Check whether the iteration count used differs from the standard number.
212 $countLog2 = $this->getCountLog2($saltedPW);
213 return !is_null($countLog2) && $countLog2 < $this->getHashCount();
214 }
215
216 /**
217 * Method determines if a given string is a valid salt.
218 *
219 * Overwrites {@link Md5Salt::isValidSalt()} with
220 * Blowfish specifics.
221 *
222 * @param string $salt String to check
223 * @return bool TRUE if it's valid salt, otherwise FALSE
224 */
225 public function isValidSalt($salt)
226 {
227 $isValid = ($skip = false);
228 $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
229 if (strlen($salt) >= $reqLenBase64) {
230 // Salt with prefixed setting
231 if (!strncmp('$', $salt, 1)) {
232 if (!strncmp($this->getSetting(), $salt, strlen($this->getSetting()))) {
233 $isValid = true;
234 $salt = substr($salt, strrpos($salt, '$') + 1);
235 } else {
236 $skip = true;
237 }
238 }
239 // Checking base64 characters
240 if (!$skip && strlen($salt) >= $reqLenBase64) {
241 if (preg_match('/^[' . preg_quote($this->getItoa64(), '/') . ']{' . $reqLenBase64 . ',' . $reqLenBase64 . '}$/', substr($salt, 0, $reqLenBase64))) {
242 $isValid = true;
243 }
244 }
245 }
246 return $isValid;
247 }
248
249 /**
250 * Method determines if a given string is a valid salted hashed password.
251 *
252 * @param string $saltedPW String to check
253 * @return bool TRUE if it's valid salted hashed password, otherwise FALSE
254 */
255 public function isValidSaltedPW($saltedPW)
256 {
257 $isValid = !strncmp($this->getSetting(), $saltedPW, strlen($this->getSetting()));
258 if ($isValid) {
259 $isValid = $this->isValidSalt($saltedPW);
260 }
261 return $isValid;
262 }
263
264 /**
265 * Method sets log2 number of iterations for password stretching.
266 *
267 * @param int $hashCount log2 number of iterations for password stretching to set
268 * @see HASH_COUNT
269 * @see $hashCount
270 * @see getHashCount()
271 */
272 public function setHashCount($hashCount = null)
273 {
274 self::$hashCount = !is_null($hashCount) && is_int($hashCount) && $hashCount >= $this->getMinHashCount() && $hashCount <= $this->getMaxHashCount() ? $hashCount : self::HASH_COUNT;
275 }
276
277 /**
278 * Method sets maximum allowed log2 number of iterations for password stretching.
279 *
280 * @param int $maxHashCount Maximum allowed log2 number of iterations for password stretching to set
281 * @see MAX_HASH_COUNT
282 * @see $maxHashCount
283 * @see getMaxHashCount()
284 */
285 public function setMaxHashCount($maxHashCount = null)
286 {
287 self::$maxHashCount = !is_null($maxHashCount) && is_int($maxHashCount) ? $maxHashCount : self::MAX_HASH_COUNT;
288 }
289
290 /**
291 * Method sets minimum allowed log2 number of iterations for password stretching.
292 *
293 * @param int $minHashCount Minimum allowed log2 number of iterations for password stretching to set
294 * @see MIN_HASH_COUNT
295 * @see $minHashCount
296 * @see getMinHashCount()
297 */
298 public function setMinHashCount($minHashCount = null)
299 {
300 self::$minHashCount = !is_null($minHashCount) && is_int($minHashCount) ? $minHashCount : self::MIN_HASH_COUNT;
301 }
302 }