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