0543b4380942e641896f97c1d2cd8d28c63423a4
[Packages/TYPO3.CMS.git] / typo3 / sysext / saltedpasswords / Classes / Salt / Md5Salt.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 use TYPO3\CMS\Core\Crypto\Random;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19
20 /**
21 * Class that implements MD5 salted hashing based on PHP's
22 * crypt() function.
23 *
24 * MD5 salted hashing with PHP's crypt() should be available
25 * on most of the systems.
26 */
27 class Md5Salt extends AbstractSalt implements SaltInterface
28 {
29 /**
30 * Keeps a string for mapping an int to the corresponding
31 * base 64 character.
32 */
33 const ITOA64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
34
35 /**
36 * Keeps length of a MD5 salt in bytes.
37 *
38 * @var int
39 */
40 protected static $saltLengthMD5 = 6;
41
42 /**
43 * Keeps suffix to be appended to a salt.
44 *
45 * @var string
46 */
47 protected static $saltSuffixMD5 = '$';
48
49 /**
50 * Setting string to indicate type of hashing method (md5).
51 *
52 * @var string
53 */
54 protected static $settingMD5 = '$1$';
55
56 /**
57 * Method applies settings (prefix, suffix) to a salt.
58 *
59 * @param string $salt A salt to apply setting to
60 * @return string Salt with setting
61 */
62 protected function applySettingsToSalt($salt)
63 {
64 $saltWithSettings = $salt;
65 $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
66 // Salt without setting
67 if (strlen($salt) == $reqLenBase64) {
68 $saltWithSettings = $this->getSetting() . $salt . $this->getSaltSuffix();
69 }
70 return $saltWithSettings;
71 }
72
73 /**
74 * Method checks if a given plaintext password is correct by comparing it with
75 * a given salted hashed password.
76 *
77 * @param string $plainPW plain-text password to compare with salted hash
78 * @param string $saltedHashPW salted hash to compare plain-text password with
79 * @return bool TRUE, if plain-text password matches the salted hash, otherwise FALSE
80 */
81 public function checkPassword($plainPW, $saltedHashPW)
82 {
83 $isCorrect = false;
84 if ($this->isValidSalt($saltedHashPW)) {
85 $isCorrect = \password_verify($plainPW, $saltedHashPW);
86 }
87 return $isCorrect;
88 }
89
90 /**
91 * Generates a random base 64-encoded salt prefixed and suffixed with settings for the hash.
92 *
93 * Proper use of salts may defeat a number of attacks, including:
94 * - The ability to try candidate passwords against multiple hashes at once.
95 * - The ability to use pre-hashed lists of candidate passwords.
96 * - The ability to determine whether two users have the same (or different)
97 * password without actually having to guess one of the passwords.
98 *
99 * @return string A character string containing settings and a random salt
100 */
101 protected function getGeneratedSalt()
102 {
103 $randomBytes = GeneralUtility::makeInstance(Random::class)->generateRandomBytes($this->getSaltLength());
104 return $this->base64Encode($randomBytes, $this->getSaltLength());
105 }
106
107 /**
108 * Method creates a salted hash for a given plaintext password
109 *
110 * @param string $password plaintext password to create a salted hash from
111 * @param string $salt Optional custom salt with setting to use
112 * @return string Salted hashed password
113 */
114 public function getHashedPassword($password, $salt = null)
115 {
116 $saltedPW = null;
117 if (!empty($password)) {
118 if (empty($salt) || !$this->isValidSalt($salt)) {
119 $salt = $this->getGeneratedSalt();
120 }
121 $saltedPW = crypt($password, $this->applySettingsToSalt($salt));
122 }
123 return $saltedPW;
124 }
125
126 /**
127 * Returns a string for mapping an int to the corresponding base 64 character.
128 *
129 * @return string String for mapping an int to the corresponding base 64 character
130 */
131 protected function getItoa64()
132 {
133 return self::ITOA64;
134 }
135
136 /**
137 * Returns whether all prerequisites for the hashing methods are matched
138 *
139 * @return bool Method available
140 */
141 public function isAvailable()
142 {
143 return CRYPT_MD5;
144 }
145
146 /**
147 * Returns length of a MD5 salt in bytes.
148 *
149 * @return int Length of a MD5 salt in bytes
150 */
151 public function getSaltLength()
152 {
153 return self::$saltLengthMD5;
154 }
155
156 /**
157 * Returns suffix to be appended to a salt.
158 *
159 * @return string Suffix of a salt
160 */
161 protected function getSaltSuffix()
162 {
163 return self::$saltSuffixMD5;
164 }
165
166 /**
167 * Returns setting string of MD5 salted hashes.
168 *
169 * @return string Setting string of MD5 salted hashes
170 */
171 public function getSetting()
172 {
173 return self::$settingMD5;
174 }
175
176 /**
177 * Checks whether a user's hashed password needs to be replaced with a new hash.
178 *
179 * This is typically called during the login process when the plain text
180 * password is available. A new hash is needed when the desired iteration
181 * count has changed through a change in the variable $hashCount or
182 * HASH_COUNT or if the user's password hash was generated in an bulk update
183 * with class ext_update.
184 *
185 * @param string $passString Salted hash to check if it needs an update
186 * @return bool TRUE if salted hash needs an update, otherwise FALSE
187 */
188 public function isHashUpdateNeeded($passString)
189 {
190 return false;
191 }
192
193 /**
194 * Method determines if a given string is a valid salt
195 *
196 * @param string $salt String to check
197 * @return bool TRUE if it's valid salt, otherwise FALSE
198 */
199 public function isValidSalt($salt)
200 {
201 $isValid = ($skip = false);
202 $reqLenBase64 = $this->getLengthBase64FromBytes($this->getSaltLength());
203 if (strlen($salt) >= $reqLenBase64) {
204 // Salt with prefixed setting
205 if (!strncmp('$', $salt, 1)) {
206 if (!strncmp($this->getSetting(), $salt, strlen($this->getSetting()))) {
207 $isValid = true;
208 $salt = substr($salt, strlen($this->getSetting()));
209 } else {
210 $skip = true;
211 }
212 }
213 // Checking base64 characters
214 if (!$skip && strlen($salt) >= $reqLenBase64) {
215 if (preg_match('/^[' . preg_quote($this->getItoa64(), '/') . ']{' . $reqLenBase64 . ',' . $reqLenBase64 . '}$/', substr($salt, 0, $reqLenBase64))) {
216 $isValid = true;
217 }
218 }
219 }
220 return $isValid;
221 }
222
223 /**
224 * Method determines if a given string is a valid salted hashed password.
225 *
226 * @param string $saltedPW String to check
227 * @return bool TRUE if it's valid salted hashed password, otherwise FALSE
228 */
229 public function isValidSaltedPW($saltedPW)
230 {
231 $isValid = !strncmp($this->getSetting(), $saltedPW, strlen($this->getSetting()));
232 if ($isValid) {
233 $isValid = $this->isValidSalt($saltedPW);
234 }
235 return $isValid;
236 }
237 }