1+ <?php
2+
3+ /**
4+ * @file
5+ * Secure password hashing functions for user authentication.
6+ * Adopted from Drupal 7.x WD 2018-01-04
7+ *
8+ * Based on the Portable PHP password hashing framework.
9+ * @see http://www.openwall.com/phpass/
10+ *
11+ * An alternative or custom version of this password hashing API may be
12+ * used by setting the variable password_inc to the name of the PHP file
13+ * containing replacement user_hash_password(), user_check_password(), and
14+ * user_needs_new_hash() functions.
15+ */
16+
17+ /**
18+ * The standard log2 number of iterations for password stretching. This should
19+ * increase by 1 every Drupal version in order to counteract increases in the
20+ * speed and power of computers available to crack the hashes.
21+ */
22+ define ('HASH_COUNT ' , 15 );
23+
24+ /**
25+ * The minimum allowed log2 number of iterations for password stretching.
26+ */
27+ define ('MIN_HASH_COUNT ' , 7 );
28+
29+ /**
30+ * The maximum allowed log2 number of iterations for password stretching.
31+ */
32+ define ('MAX_HASH_COUNT ' , 30 );
33+
34+ /**
35+ * The expected (and maximum) number of characters in a hashed password.
36+ */
37+ define ('HASH_LENGTH ' , 55 );
38+
39+ /**
40+ * Returns a string for mapping an int to the corresponding base 64 character.
41+ */
42+ function _password_itoa64 () {
43+ return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ' ;
44+ }
45+
46+ /**
47+ * Encodes bytes into printable base 64 using the *nix standard from crypt().
48+ *
49+ * @param $input
50+ * The string containing bytes to encode.
51+ * @param $count
52+ * The number of characters (bytes) to encode.
53+ *
54+ * @return
55+ * Encoded string
56+ */
57+ function _password_base64_encode ($ input , $ count ) {
58+ $ output = '' ;
59+ $ i = 0 ;
60+ $ itoa64 = _password_itoa64 ();
61+ do {
62+ $ value = ord ($ input [$ i ++]);
63+ $ output .= $ itoa64 [$ value & 0x3f ];
64+ if ($ i < $ count ) {
65+ $ value |= ord ($ input [$ i ]) << 8 ;
66+ }
67+ $ output .= $ itoa64 [($ value >> 6 ) & 0x3f ];
68+ if ($ i ++ >= $ count ) {
69+ break ;
70+ }
71+ if ($ i < $ count ) {
72+ $ value |= ord ($ input [$ i ]) << 16 ;
73+ }
74+ $ output .= $ itoa64 [($ value >> 12 ) & 0x3f ];
75+ if ($ i ++ >= $ count ) {
76+ break ;
77+ }
78+ $ output .= $ itoa64 [($ value >> 18 ) & 0x3f ];
79+ } while ($ i < $ count );
80+
81+ return $ output ;
82+ }
83+ /**
84+ * Returns a string of highly randomized bytes (over the full 8-bit range).
85+ *
86+ * This function is better than simply calling mt_rand() or any other built-in
87+ * PHP function because it can return a long string of bytes (compared to < 4
88+ * bytes normally from mt_rand()) and uses the best available pseudo-random
89+ * source.
90+ *
91+ * @param $count
92+ * The number of characters (bytes) to return in the string.
93+ */
94+
95+ function _random_bytes ($ count ) {
96+ // $random_state does not use static as it stores random bytes.
97+ static $ random_state , $ bytes , $ has_openssl ;
98+
99+ $ missing_bytes = $ count - strlen ($ bytes );
100+
101+ if ($ missing_bytes > 0 ) {
102+ // PHP versions prior 5.3.4 experienced openssl_random_pseudo_bytes()
103+ // locking on Windows and rendered it unusable.
104+ if (!isset ($ has_openssl )) {
105+ $ has_openssl = version_compare (PHP_VERSION , '5.3.4 ' , '>= ' ) && function_exists ('openssl_random_pseudo_bytes ' );
106+ }
107+
108+ // openssl_random_pseudo_bytes() will find entropy in a system-dependent
109+ // way.
110+ if ($ has_openssl ) {
111+ $ bytes .= openssl_random_pseudo_bytes ($ missing_bytes );
112+ }
113+
114+ // Else, read directly from /dev/urandom, which is available on many *nix
115+ // systems and is considered cryptographically secure.
116+ elseif ($ fh = @fopen ('/dev/urandom ' , 'rb ' )) {
117+ // PHP only performs buffered reads, so in reality it will always read
118+ // at least 4096 bytes. Thus, it costs nothing extra to read and store
119+ // that much so as to speed any additional invocations.
120+ $ bytes .= fread ($ fh , max (4096 , $ missing_bytes ));
121+ fclose ($ fh );
122+ }
123+
124+ // If we couldn't get enough entropy, this simple hash-based PRNG will
125+ // generate a good set of pseudo-random bytes on any system.
126+ // Note that it may be important that our $random_state is passed
127+ // through hash() prior to being rolled into $output, that the two hash()
128+ // invocations are different, and that the extra input into the first one -
129+ // the microtime() - is prepended rather than appended. This is to avoid
130+ // directly leaking $random_state via the $output stream, which could
131+ // allow for trivial prediction of further "random" numbers.
132+ if (strlen ($ bytes ) < $ count ) {
133+ // Initialize on the first call. The contents of $_SERVER includes a mix of
134+ // user-specific and system information that varies a little with each page.
135+ if (!isset ($ random_state )) {
136+ $ random_state = print_r ($ _SERVER , TRUE );
137+ if (function_exists ('getmypid ' )) {
138+ // Further initialize with the somewhat random PHP process ID.
139+ $ random_state .= getmypid ();
140+ }
141+ $ bytes = '' ;
142+ }
143+
144+ do {
145+ $ random_state = hash ('sha256 ' , microtime () . mt_rand () . $ random_state );
146+ $ bytes .= hash ('sha256 ' , mt_rand () . $ random_state , TRUE );
147+ }
148+ while (strlen ($ bytes ) < $ count );
149+ }
150+ }
151+ $ output = substr ($ bytes , 0 , $ count );
152+ $ bytes = substr ($ bytes , $ count );
153+ return $ output ;
154+ }
155+
156+ /**
157+ * Generates a random base 64-encoded salt prefixed with settings for the hash.
158+ *
159+ * Proper use of salts may defeat a number of attacks, including:
160+ * - The ability to try candidate passwords against multiple hashes at once.
161+ * - The ability to use pre-hashed lists of candidate passwords.
162+ * - The ability to determine whether two users have the same (or different)
163+ * password without actually having to guess one of the passwords.
164+ *
165+ * @param $count_log2
166+ * Integer that determines the number of iterations used in the hashing
167+ * process. A larger value is more secure, but takes more time to complete.
168+ *
169+ * @return
170+ * A 12 character string containing the iteration count and a random salt.
171+ */
172+ function _password_generate_salt ($ count_log2 ) {
173+ $ output = '$S$ ' ;
174+ // Ensure that $count_log2 is within set bounds.
175+ $ count_log2 = _password_enforce_log2_boundaries ($ count_log2 );
176+ // We encode the final log2 iteration count in base 64.
177+ $ itoa64 = _password_itoa64 ();
178+ $ output .= $ itoa64 [$ count_log2 ];
179+ // 6 bytes is the standard salt for a portable phpass hash.
180+ $ output .= _password_base64_encode (_random_bytes (6 ), 6 );
181+ return $ output ;
182+ }
183+
184+ /**
185+ * Ensures that $count_log2 is within set bounds.
186+ *
187+ * @param $count_log2
188+ * Integer that determines the number of iterations used in the hashing
189+ * process. A larger value is more secure, but takes more time to complete.
190+ *
191+ * @return
192+ * Integer within set bounds that is closest to $count_log2.
193+ */
194+ function _password_enforce_log2_boundaries ($ count_log2 ) {
195+ if ($ count_log2 < MIN_HASH_COUNT ) {
196+ return MIN_HASH_COUNT ;
197+ }
198+ elseif ($ count_log2 > MAX_HASH_COUNT ) {
199+ return MAX_HASH_COUNT ;
200+ }
201+
202+ return (int ) $ count_log2 ;
203+ }
204+
205+ /**
206+ * Hash a password using a secure stretched hash.
207+ *
208+ * By using a salt and repeated hashing the password is "stretched". Its
209+ * security is increased because it becomes much more computationally costly
210+ * for an attacker to try to break the hash by brute-force computation of the
211+ * hashes of a large number of plain-text words or strings to find a match.
212+ *
213+ * @param $algo
214+ * The string name of a hashing algorithm usable by hash(), like 'sha256'.
215+ * @param $password
216+ * Plain-text password up to 512 bytes (128 to 512 UTF-8 characters) to hash.
217+ * @param $setting
218+ * An existing hash or the output of _password_generate_salt(). Must be
219+ * at least 12 characters (the settings and salt).
220+ *
221+ * @return
222+ * A string containing the hashed password (and salt) or FALSE on failure.
223+ * The return string will be truncated at DRUPAL_HASH_LENGTH characters max.
224+ */
225+ function _password_crypt ($ algo , $ password , $ setting ) {
226+ // Prevent DoS attacks by refusing to hash large passwords.
227+ if (strlen ($ password ) > 512 ) {
228+ return FALSE ;
229+ }
230+ // The first 12 characters of an existing hash are its setting string.
231+ $ setting = substr ($ setting , 0 , 12 );
232+
233+ if ($ setting [0 ] != '$ ' || $ setting [2 ] != '$ ' ) {
234+ return FALSE ;
235+ }
236+ $ count_log2 = _password_get_count_log2 ($ setting );
237+ // Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT
238+ if ($ count_log2 < MIN_HASH_COUNT || $ count_log2 > MAX_HASH_COUNT ) {
239+ return FALSE ;
240+ }
241+ $ salt = substr ($ setting , 4 , 8 );
242+ // Hashes must have an 8 character salt.
243+ if (strlen ($ salt ) != 8 ) {
244+ return FALSE ;
245+ }
246+
247+ // Convert the base 2 logarithm into an integer.
248+ $ count = 1 << $ count_log2 ;
249+
250+ // We rely on the hash() function being available in PHP 5.2+.
251+ $ hash = hash ($ algo , $ salt . $ password , TRUE );
252+ do {
253+ $ hash = hash ($ algo , $ hash . $ password , TRUE );
254+ } while (--$ count );
255+
256+ $ len = strlen ($ hash );
257+ $ output = $ setting . _password_base64_encode ($ hash , $ len );
258+ // _password_base64_encode() of a 16 byte MD5 will always be 22 characters.
259+ // _password_base64_encode() of a 64 byte sha512 will always be 86 characters.
260+ $ expected = 12 + ceil ((8 * $ len ) / 6 );
261+ return (strlen ($ output ) == $ expected ) ? substr ($ output , 0 , HASH_LENGTH ) : FALSE ;
262+ }
263+
264+ /**
265+ * Parse the log2 iteration count from a stored hash or setting string.
266+ */
267+ function _password_get_count_log2 ($ setting ) {
268+ $ itoa64 = _password_itoa64 ();
269+ return strpos ($ itoa64 , $ setting [3 ]);
270+ }
271+
272+ /**
273+ * Hash a password using a secure hash.
274+ *
275+ * @param $password
276+ * A plain-text password.
277+ * @param $count_log2
278+ * Optional integer to specify the iteration count. Generally used only during
279+ * mass operations where a value less than the default is needed for speed.
280+ *
281+ * @return
282+ * A string containing the hashed password (and a salt), or FALSE on failure.
283+ */
284+ function user_hash_password ($ password , $ count_log2 = 0 ) {
285+ if (empty ($ count_log2 )) {
286+ // Use the standard iteration count.
287+ $ count_log2 = variable_get ('password_count_log2 ' , DRUPAL_HASH_COUNT );
288+ }
289+ return _password_crypt ('sha512 ' , $ password , _password_generate_salt ($ count_log2 ));
290+ }
291+
292+ /**
293+ * Check whether a plain text password matches a stored hashed password.
294+ *
295+ * @param $password
296+ * A plain-text password
297+ * @param $hashpass
298+ *
299+ * @return
300+ * TRUE or FALSE.
301+ */
302+ function user_check_password ($ password , $ hashpass ) {
303+ $ stored_hash = $ hashpass ;
304+ $ type = substr ($ stored_hash , 0 , 3 );
305+ switch ($ type ) {
306+ case '$S$ ' :
307+ // A normal Drupal 7 password using sha512.
308+ $ hash = _password_crypt ('sha512 ' , $ password , $ stored_hash );
309+ break ;
310+ case '$H$ ' :
311+ // phpBB3 uses "$H$" for the same thing as "$P$".
312+ case '$P$ ' :
313+ // A phpass password generated using md5. This is an
314+ // imported password or from an earlier Drupal version.
315+ $ hash = _password_crypt ('md5 ' , $ password , $ stored_hash );
316+ break ;
317+ default :
318+ return FALSE ;
319+ }
320+ return ($ hash && $ stored_hash == $ hash );
321+ }
0 commit comments