PageRenderTime 76ms CodeModel.GetById 66ms app.highlight 7ms RepoModel.GetById 1ms app.codeStats 0ms

/security/Password.php

http://github.com/UnionOfRAD/lithium
PHP | 238 lines | 68 code | 22 blank | 148 comment | 9 complexity | 08567d4ba44eeb10c0441ef900226d04 MD5 | raw file
  1<?php
  2/**
  3 * li₃: the most RAD framework for PHP (http://li3.me)
  4 *
  5 * Copyright 2010, Union of RAD. All rights reserved. This source
  6 * code is distributed under the terms of the BSD 3-Clause License.
  7 * The full license text can be found in the LICENSE.txt file.
  8 */
  9
 10namespace lithium\security;
 11
 12use lithium\security\Hash;
 13use lithium\security\Random;
 14
 15/**
 16 * `Password` utility class that makes use of PHP's `crypt()` function. Includes a
 17 * cryptographically strong salt generator, and utility functions to hash and check
 18 * passwords.
 19 */
 20class Password {
 21
 22	/**
 23	 * The default log2 number of iterations for Blowfish encryption.
 24	 */
 25	const BF = 10;
 26
 27	/**
 28	 * The default log2 number of iterations for XDES encryption.
 29	 */
 30	const XDES = 18;
 31
 32	/**
 33	 * Hashes a password using PHP's `crypt()` and an optional salt. If no
 34	 * salt is supplied, a cryptographically strong salt will be generated
 35	 * using `lithium\security\Password::salt()`.
 36	 *
 37	 * Using this function is the proper way to hash a password. Using naïve
 38	 * methods such as sha1 or md5, as is done in many web applications, is
 39	 * improper due to the lack of a cryptographically strong salt.
 40	 *
 41	 * Using `lithium\security\Password::hash()` ensures that:
 42	 *
 43	 * - Two identical passwords will never use the same salt, thus never
 44	 *   resulting in the same hash; this prevents a potential attacker from
 45	 *   compromising user accounts by using a database of most commonly used
 46	 *   passwords.
 47	 * - The salt generator's count iterator can be increased within Lithium
 48	 *   or your application as computer hardware becomes faster; this results
 49	 *   in slower hash generation, without invalidating existing passwords.
 50	 *
 51	 * Usage:
 52	 *
 53	 * ```
 54	 * // Hash a password before storing it:
 55	 * $hashed  = Password::hash($password);
 56	 *
 57	 * // Check a password by comparing it to its hashed value:
 58	 * $check   = Password::check($password, $hashed);
 59	 *
 60	 * // Use a stronger custom salt:
 61	 * $salt    = Password::salt('bf', 16); // 2^16 iterations
 62	 * $hashed  = Password::hash($password, $salt); // Very slow
 63	 * $check   = Password::check($password, $hashed); // Very slow
 64	 *
 65	 * // Forward/backward compatibility
 66	 * $salt1   = Password::salt('bf', 6);
 67	 * $salt2   = Password::salt('bf', 12);
 68	 * $hashed1 = Password::hash($password, $salt1); // Fast
 69	 * $hashed2 = Password::hash($password, $salt2); // Slow
 70	 * $check1  = Password::check($password, $hashed1); // True
 71	 * $check2  = Password::check($password, $hashed2); // True
 72	 * ```
 73	 *
 74	 * @see lithium\security\Password::check()
 75	 * @see lithium\security\Password::salt()
 76	 * @link http://php.net/manual/function.crypt.php
 77	 * @param string $password The password to hash.
 78	 * @param string $salt Optional. The salt string.
 79	 * @return string The hashed password.
 80	 *        The result's length will be:
 81	 *        - 60 chars long for Blowfish hashes
 82	 *        - 20 chars long for XDES hashes
 83	 *        - 34 chars long for MD5 hashes
 84	 */
 85	public static function hash($password, $salt = null) {
 86		return crypt($password, $salt ?: static::salt());
 87	}
 88
 89	/**
 90	 * Compares a password and its hashed value using PHP's `crypt()`. Rather than a simple string
 91	 * comparison, this method uses a constant-time algorithm to defend against timing attacks.
 92	 *
 93	 * @see lithium\security\Password::hash()
 94	 * @see lithium\security\Password::salt()
 95	 * @param string $password The user-supplied plaintext password to check.
 96	 * @param string $hash The known hashed password to compare it to.
 97	 * @return boolean Returns a boolean indicating whether the password is correct.
 98	 */
 99	public static function check($password, $hash) {
100		return Hash::compare($hash, crypt($password, $hash));
101	}
102
103	/**
104	 * Generates a cryptographically strong salt, using the best available
105	 * method (tries Blowfish, then XDES, and fallbacks to MD5), for use in
106	 * `Password::hash()`.
107	 *
108	 * Blowfish and XDES are adaptive hashing algorithms. MD5 is not. Adaptive
109	 * hashing algorithms are designed in such a way that when computers get
110	 * faster, you can tune the algorithm to be slower by increasing the number
111	 * of hash iterations, without introducing incompatibility with existing
112	 * passwords.
113	 *
114	 * To pick an appropriate iteration count for adaptive algorithms, consider
115	 * that the original DES crypt was designed to have the speed of 4 hashes
116	 * per second on the hardware of that time. Slower than 4 hashes per second
117	 * would probably dampen usability. Faster than 100 hashes per second is
118	 * probably too fast. The defaults generate about 10 hashes per second
119	 * using a dual-core 2.2GHz CPU.
120	 *
121	 *  _Note 1_: this salt generator is different from naive salt implementations
122	 * (e.g. `md5(microtime())`) in that it uses all of the available bits of
123	 * entropy for the supplied salt method.
124	 *
125	 *  _Note2_: this method should not be use to generate custom salts. Indeed,
126	 * the resulting salts are prefixed with information expected by PHP's
127	 * `crypt()`. To get an arbitrarily long, cryptographically strong salt
128	 * consisting in random sequences of alpha numeric characters, use
129	 * `lithium\security\Random::generate()` instead.
130	 *
131	 * @link http://php.net/function.crypt.php
132	 * @link http://www.postgresql.org/docs/9.0/static/pgcrypto.html
133	 * @see lithium\security\Password::hash()
134	 * @see lithium\security\Password::check()
135	 * @see lithium\security\Random::generate()
136	 * @param string $type The hash type. Optional. Defaults to the best
137	 *        available option. Supported values, along with their maximum
138	 *        password lengths, include:
139	 *        - `'bf'`: Blowfish (128 salt bits, max 72 chars)
140	 *        - `'xdes'`: XDES (24 salt bits, max 8 chars)
141	 *        - `'md5'`: MD5 (48 salt bits, unlimited length)
142	 * @param integer $count Optional. The base-2 logarithm of the iteration
143	 *        count, for adaptive algorithms. Defaults to:
144	 *        - `10` for Blowfish
145	 *        - `18` for XDES
146	 * @return string The salt string.
147	 */
148	public static function salt($type = null, $count = null) {
149		switch (true) {
150			case CRYPT_BLOWFISH === 1 && (!$type || $type === 'bf'):
151				return static::_generateSaltBf($count);
152			case CRYPT_EXT_DES === 1 && (!$type || $type === 'xdes'):
153				return static::_generateSaltXdes($count);
154			default:
155				return static::_generateSaltMd5();
156		}
157	}
158
159	/**
160	 * Generates a Blowfish salt for use in `lithium\security\Password::hash()`. _Note_: Does not
161	 * use the `'encode'` option of `Random::generate()` because it could result in 2 bits less of
162	 * entropy depending on the last character.
163	 *
164	 * @param integer $count The base-2 logarithm of the iteration count.
165	 *        Defaults to `10`. Can be `4` to `31`.
166	 * @return string The Blowfish salt.
167	 */
168	protected static function _generateSaltBf($count = null) {
169		$count = (integer) $count;
170		$count = ($count < 4 || $count > 31) ? static::BF : $count;
171
172		$base64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
173		$i = 0;
174
175		$input = Random::generate(16);
176		$output = '';
177
178		do {
179			$c1 = ord($input[$i++]);
180			$output .= $base64[$c1 >> 2];
181			$c1 = ($c1 & 0x03) << 4;
182			if ($i >= 16) {
183				$output .= $base64[$c1];
184				break;
185			}
186
187			$c2 = ord($input[$i++]);
188			$c1 |= $c2 >> 4;
189			$output .= $base64[$c1];
190			$c1 = ($c2 & 0x0f) << 2;
191
192			$c2 = ord($input[$i++]);
193			$c1 |= $c2 >> 6;
194			$output .= $base64[$c1];
195			$output .= $base64[$c2 & 0x3f];
196		} while (1);
197
198		$result = '$2a$';
199		$result .= chr(ord('0') + $count / static::BF);
200		$result .= chr(ord('0') + $count % static::BF);
201		$result .= '$' . $output;
202
203		return $result;
204	}
205
206	/**
207	 * Generates an Extended DES salt for use in `lithium\security\Password::hash()`.
208	 *
209	 * @param integer $count The base-2 logarithm of the iteration count. Defaults to `18`. Can be
210	 *                `1` to `24`. 1 will be stripped from the non-log value, e.g. 2^18 - 1, to
211	 *                ensure we don't use a weak DES key.
212	 * @return string The XDES salt.
213	 */
214	protected static function _generateSaltXdes($count = null) {
215		$count = (integer) $count;
216		$count = ($count < 1 || $count > 24) ? static::XDES : $count;
217
218		$count = (1 << $count) - 1;
219		$base64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
220
221		$output = '_' . $base64[$count & 0x3f] . $base64[($count >> 6) & 0x3f];
222		$output .= $base64[($count >> 12) & 0x3f] . $base64[($count >> 18) & 0x3f];
223		$output .= Random::generate(3, ['encode' => Random::ENCODE_BASE_64]);
224
225		return $output;
226	}
227
228	/**
229	 * Generates an MD5 salt for use in `lithium\security\Password::hash()`.
230	 *
231	 * @return string The MD5 salt.
232	 */
233	protected static function _generateSaltMd5() {
234		return '$1$' . Random::generate(6, ['encode' => Random::ENCODE_BASE_64]);
235	}
236}
237
238?>