Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

introduce \lithium\security\Crypto and \lithium\security\Password

  • Loading branch information...
commit 3cd874d7a8bd25be7b876ce94f59b0a156cdb02d 1 parent 860cffb
@ddebernardy ddebernardy authored nateabele committed
View
146 libraries/lithium/security/Crypto.php
@@ -0,0 +1,146 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org)
+ * Copyright 2009, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @license http://opensource.org/licenses/mit-license.php The MIT License
+ */
+
+namespace lithium\security;
+
+use \Exception;
+
+/**
+ * Cryptographic utility class. Includes a random number generator, and a base64
+ * encoder for use with DES and XDES.
+ *
+ * @see lithium\security\Password
+ */
+class Crypto {
+ /**
+ * A closure which, given a number of bytes, returns that amount of
+ * random bytes.
+ *
+ * @var Closure
+ */
+ protected static $_source;
+
+ /**
+ * Generates random bytes for use in UUIDs and password salts.
+ *
+ * The method seeds the random source automatically. It uses
+ * /dev/urandom if the latter is available; `md_rand()` if not.
+ *
+ * It can also be used to generate arbitrary bits:
+ *
+ * {{{
+ * $bits = bin2hex(String::random(8)); // 64 bits
+ * }}}
+ *
+ * @param integer $bytes The number of random bytes to generate
+ * @param string Random bytes
+ */
+ public static function random($bytes) {
+ $source = static::$_source ?: static::_source();
+ return $source($bytes);
+ }
+
+ /**
+ * Encodes bytes into an `./0-9A-Za-z` alphabet, for use as salt when
+ * hashing passwords.
+ *
+ * Note: this is not the same as RFC 1421, or `base64_encode()`, which
+ * uses an `+/0-9A-Za-z` alphabet.
+ *
+ * This function can be combined with `Crypto::random()` to generate random
+ * sequences of `./0-9A-Za-z` characters:
+ *
+ * {{{
+ * $salt = String::encode64(String::random(8)); // 64 bits
+ * }}}
+ *
+ * @param string $input The input bytes.
+ * @return string The same bytes in the `/.0-9A-Za-z` alphabet.
+ * @see lithium\security\Crypto::random()
+ */
+ public static function encode64($input) {
+ $base64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+ $i = 0;
+
+ $count = strlen($input);
+ $output = '';
+
+ do {
+ $value = ord($input[$i++]);
+ $output .= $base64[$value & 0x3f];
+
+ if ($i < $count) {
+ $value |= ord($input[$i]) << 8;
+ }
+ $output .= $base64[($value >> 6) & 0x3f];
+
+ if ($i++ >= $count) {
+ break;
+ }
+ if ($i < $count) {
+ $value |= ord($input[$i]) << 16;
+ }
+ $output .= $base64[($value >> 12) & 0x3f];
+
+ if ($i++ >= $count) {
+ break;
+ }
+ $output .= $base64[($value >> 18) & 0x3f];
+ } while ($i < $count);
+
+ return $output;
+ }
+
+ /**
+ * Configures Crypto::_$source using the best available random
+ * number generator, or the supplied Closure.
+ *
+ * On *nix systems, /dev/urandom gets used if available. On Windows
+ * systems, COM gets used if available.
+ *
+ * If all else fails, a Mersenne Twister gets used. (Strictly speaking,
+ * this fallback is inadequate, but good enough.)
+ *
+ * @return Closure The random number generator.
+ **/
+ protected static function _source() {
+ switch (true) {
+ case isset(static::$_source);
+ return static::$_source;
+
+ case is_readable('/dev/urandom') && $fp = fopen('/dev/urandom', 'rb'):
+ return static::$_source = function($bytes) use (&$fp) {
+ return fread($fp, $bytes);
+ };
+
+ case class_exists('COM', 0):
+ // http://msdn.microsoft.com/en-us/library/aa388182(VS.85).aspx
+ try {
+ $com = new COM('CAPICOM.Utilities.1');
+ return static::$_source = function($bytes) use ($com) {
+ return base64_decode($com->GetRandom($bytes,0));
+ };
+ }
+ catch (Exception $e) {
+ }
+
+ default:
+ // fallback to using mt_rand() if all else fails
+ return static::$_source = function($bytes) {
+ $rand = '';
+ for ($i = 0; $i < $bytes; $i++) {
+ $rand .= chr(mt_rand(0, 255));
+ }
+ return $rand;
+ };
+ }
+ }
+}
+
+?>
View
232 libraries/lithium/security/Password.php
@@ -0,0 +1,232 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org)
+ * Copyright 2009, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @license http://opensource.org/licenses/mit-license.php The MIT License
+ */
+
+namespace lithium\security;
+
+/**
+ * Password utility class that makes use of PHP's `crypt()` function. Includes a
+ * cryptographically strong salt generator, and utility functions to hash and check
+ * passwords.
+ */
+class Password extends \lithium\security\Crypto {
+ /**
+ * Hashes a password using PHP's `crypt()` and an optional salt. If no
+ * salt is supplied, a cryptographically strong salt will be generated
+ * using `lithium\security\Password::genSalt()`.
+ *
+ * Using this function is the proper way to hash a password. Using naive
+ * methods such as sha1 or md5, as is done in many web applications, is
+ * improper due to the lack of a cryptographically strong salt.
+ *
+ * Using `lithium\security\Password::hash()` ensures that:
+ *
+ * - Two identical passwords will never use the same salt, thus never
+ * resulting in the same hash; this prevents a potential attacker from
+ * compromising user accounts by using a database of most commonly used
+ * passwords.
+ * - The salt generator's count interator can be increased within Lithium
+ * or your application as computer hardware becomes faster; this results
+ * in slower hash generation, without invalidating existing passwords.
+ *
+ * Usage:
+ *
+ * {{{
+ * // Hash a password before storing it:
+ * $hashed = Password::hash($password);
+ *
+ * // Check a password by comparing it to its hashed value:
+ * $check = Password::check($password, $hashed);
+ *
+ * // Use a stronger custom salt:
+ * $salt = Password::genSalt('bf', 16); // 2^16 iterations
+ * $hashed = Password::hash($password, $salt); // Very slow
+ * $check = Password::check($password, $hashed); // Very slow
+ *
+ * // Forward/backward compatibility
+ * $salt1 = Password::genSalt('bf', 6);
+ * $salt2 = Password::genSalt('bf', 12);
+ * $hashed1 = Password::hash($password, $salt1); // Fast
+ * $hashed2 = Password::hash($password, $salt2); // Slow
+ * $check1 = Password::check($password, $hashed1); // True
+ * $check2 = Password::check($password, $hashed2); // True
+ * }}}
+ *
+ * @param string $password The password to hash.
+ * @param string $salt Optional. The salt string.
+ * @return string The hashed password.
+ * The result's length will be:
+ * - 60 chars long for Blowfish hashes
+ * - 20 chars long for XDES hashes
+ * - 34 chars long for MD5 hashes
+ * @see lithium\security\Password::check()
+ * @see lithium\security\Password::genSalt()
+ **/
+ public static function hash($password, $salt = null) {
+ return crypt($password, $salt ?: static::genSalt());
+ }
+
+ /**
+ * Compares a password and its hashed value using PHP's `crypt()`.
+ *
+ * @param string $password The password to check
+ * @param string $hash The hashed password to compare it to
+ * @return boolean Whether the password is correct or not
+ * @see lithium\security\Password::hash()
+ * @see lithium\security\Password::genSalt()
+ **/
+ public static function check($password, $hash) {
+ return $hash == crypt($password, $hash);
+ }
+
+ /**
+ * Generates a cryptographically strong salt, using the best available
+ * method (tries Blowfish, then XDES, and fallbacks to MD5), for use in
+ * `Password::hash()`.
+ *
+ * Blowfish and XDES are adaptive hashing algorithms. MD5 is not. Adaptive
+ * hashing algorithms are designed in such a way that when computers get
+ * faster, you can tune the algorithm to be slower by increasing the number
+ * of hash iterations, without introducing incompatibility with existing
+ * passwords.
+ *
+ * To pick an appropriate iteration count for adaptive algorithms, consider
+ * that the original DES crypt was designed to have the speed of 4 hashes
+ * per second on the hardware of that time. Slower than 4 hashes per second
+ * would probably dampen usability. Faster than 100 hashes per second is
+ * probably too fast. The defaults generate about 10 hashes per second
+ * using a dual-core 2.2GHz CPU.
+ *
+ * Note1: this salt generator is different from naive salt implementations
+ * (e.g. `md5(microtime())`) in that it uses all of the available bits of
+ * entropy for the supplied salt method.
+ *
+ * Note2: this method should not be to generate custom salts. Indeed, the
+ * resulting salts are prefixed with information expected by PHP's
+ * `crypt()`. To get an arbitrarily long, cryptographically strong salt
+ * consisting in random sequences of alpha numeric characters, use
+ * `lithium\security\Crypto::encode64()` instead.
+ *
+ * @param string $type The hash type. Optional. Defaults to the best
+ * available option. Supported values, along with their maximum
+ * password lengths, include:
+ * - `'bf'`: Blowfish (128 salt bits, max 72 chars)
+ * - `'xdes'`: XDES (24 salt bits, max 8 chars)
+ * - `'md5'`: MD5 (48 salt bits, unlimited length)
+ * @param integer $count Optional. The base-2 logarithm of the iteration
+ * count, for adaptive algorithms. Defaults to:
+ * - `10` for Blowfish
+ * - `18` for XDES
+ * @return string The salt string.
+ * @link http://php.net/manual/en/function.crypt.php
+ * @link http://www.postgresql.org/docs/9.0/static/pgcrypto.html
+ * @see lithium\security\Password::hash()
+ * @see lithium\security\Password::check()
+ **/
+ public static function genSalt($type = null, $count = null) {
+ switch (true) {
+ case CRYPT_BLOWFISH == 1 && (!$type || $type === 'bf'):
+ return static::_genSaltBF($count);
+ case CRYPT_EXT_DES == 1 && (!$type || $type === 'xdes'):
+ return static::_genSaltXDES($count);
+ default:
+ return static::_genSaltMD5();
+ }
+ }
+
+ /**
+ * Generates a Blowfish salt for use in `lithium\security\Password::hash()`.
+ *
+ * @param integer $count The base-2 logarithm of the iteration count.
+ * Defaults to `10`. Can be `4` to `31`.
+ * @return string $salt
+ **/
+ protected static function _genSaltBf($count = 10) {
+ $count = (integer) $count;
+ if ($count < 4 || $count > 31)
+ $count = 10;
+
+ // We don't use the encode64() method here because it could result
+ // in 2 bits less of entropy depending on the last char.
+ $base64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ $i = 0;
+
+ $input = static::random(16); // 128 bits of salt
+ $output = '';
+
+ do {
+ $c1 = ord($input[$i++]);
+ $output .= $base64[$c1 >> 2];
+ $c1 = ($c1 & 0x03) << 4;
+ if ($i >= 16) {
+ $output .= $base64[$c1];
+ break;
+ }
+
+ $c2 = ord($input[$i++]);
+ $c1 |= $c2 >> 4;
+ $output .= $base64[$c1];
+ $c1 = ($c2 & 0x0f) << 2;
+
+ $c2 = ord($input[$i++]);
+ $c1 |= $c2 >> 6;
+ $output .= $base64[$c1];
+ $output .= $base64[$c2 & 0x3f];
+ } while (1);
+
+ return '$2a$'
+ // zeroize $count
+ . chr(ord('0') + $count / 10) . chr(ord('0') + $count % 10)
+ . '$' . $output;
+ }
+
+ /**
+ * Generates an Extended DES salt for use in `lithium\security\Password::hash()`.
+ *
+ * @param integer $count The base-2 logarithm of the iteration count.
+ * Defaults to `18`. Can be `1` to `24`. 1 will be stripped
+ * from the non-log value, e.g. 2^18 - 1, to ensure we don't
+ * use a weak DES key.
+ * @return string The XDES salt.
+ */
+ protected static function _genSaltXDES($count = 18) {
+ $count = (integer) $count;
+ if ($count < 1 || $count > 24)
+ $count = 16;
+
+ // Count should be odd to not reveal weak DES keys
+ $count = (1 << $count) - 1;
+
+ $base64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+
+ $output = '_'
+ // iterations
+ . $base64[$count & 0x3f]
+ . $base64[($count >> 6) & 0x3f]
+ . $base64[($count >> 12) & 0x3f]
+ . $base64[($count >> 18) & 0x3f]
+ // 24 bits of salt
+ . static::encode64(static::random(3));
+
+ return $output;
+ }
+
+ /**
+ * Generates an MD5 salt for use in `lithium\security\Password::hash()`.
+ *
+ * @return string The MD5 salt.
+ **/
+ protected static function _genSaltMD5() {
+ $output = '$1$'
+ // 48 bits of salt
+ . static::encode64(static::random(6));
+ return $output;
+ }
+}
+
+?>
View
32 libraries/lithium/tests/cases/security/CryptoTest.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org)
+ * @license http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\tests\cases\security;
+
+use \lithium\security\Crypto;
+
+class CryptoTest extends \lithium\test\Unit {
+ /**
+ * testRandomGenerator method
+ *
+ * @return void
+ **/
+ public function testRandomGenerator() {
+ $check = array();
+ $count = 50;
+ $pattern = "/^[0-9A-Za-z\.\/]+$/";
+ for ($i = 0; $i < $count; $i++) {
+ $result = Crypto::random(8);
+ $this->assertPattern($pattern, Crypto::encode64($result));
+ $this->assertFalse(in_array($result, $check));
+ $check[] = $result;
+ }
+ }
+}
+
+?>
View
57 libraries/lithium/tests/cases/security/PasswordTest.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org)
+ * @license http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium\tests\cases\security;
+
+use \lithium\security\Password;
+
+class PasswordTest extends \lithium\test\Unit {
+ /**
+ * testPassword method
+ *
+ * @return void
+ **/
+ public function testPassword() {
+ $pass = 'Lithium rocks!';
+
+ $bfSalt = "{^\\$2a\\$06\\$[0-9A-Za-z./]{22}$}";
+ $bfHash = "{^\\$2a\\$06\\$[0-9A-Za-z./]{53}$}";
+
+ $xdesSalt = "{^_zD..[0-9A-Za-z./]{4}$}";
+ $xdesHash = "{^_zD..[0-9A-Za-z./]{15}$}";
+
+ $md5Salt = "{^\\$1\\$[0-9A-Za-z./]{8}$}";
+ $md5Hash = "{^\\$1\\$[0-9A-Za-z./]{8}\\$[0-9A-Za-z./]{22}$}";
+
+ // Make it faster than the default settings, else we'll be there tomorrow
+ foreach (array('bf' => 6, 'xdes' => 10, 'md5' => false) as $method => $log2) {
+ $salts = array();
+ $hashes = array();
+ $count = 20;
+ $saltPattern = ${$method . 'Salt'};
+ $hashPattern = ${$method . 'Hash'};
+
+ for ($i = 0; $i < $count; $i++) {
+ $salt = Password::genSalt($method, $log2);
+ $this->assertPattern($saltPattern, $salt);
+ $this->assertFalse(in_array($salt, $salts));
+ $salts[] = $salt;
+
+ $hash = Password::hash($pass, $salt);
+ $this->assertPattern($hashPattern, $hash);
+ $this->assertEqual(substr($hash, 0, strlen($salt)), $salt);
+ $this->assertFalse(in_array($hash, $hashes));
+ $hashes[] = $hash;
+
+ $this->assertTrue(Password::check($pass, $hash));
+ }
+ }
+ }
+}
+
+?>
View
61 libraries/lithium/tests/cases/util/StringTest.php
@@ -13,25 +13,6 @@
use lithium\tests\mocks\util\MockStringObject;
class StringTest extends \lithium\test\Unit {
- /**
- * testRandomGenerator method
- *
- * @return void
- */
- public function testRandomGenerator() {
- // Disallow allow seeding twice
- $this->assertFalse(String::seed() && String::seed());
-
- $check = array();
- $count = 50;
- $pattern = "/^[0-9A-Za-z\.\/]+$/";
- for ($i = 0; $i < $count; $i++) {
- $result = String::random(8);
- $this->assertPattern($pattern, String::encode64($result));
- $this->assertFalse(in_array($result, $check));
- $check[] = $result;
- }
- }
/**
* testUuidGeneration method
@@ -99,48 +80,6 @@ public function testHash() {
}
/**
- * testPassword method
- *
- * @return void
- **/
- public function testPassword() {
- $pass = 'Lithium rocks!';
-
- $bfSalt = "{^\\$2a\\$06\\$[0-9A-Za-z./]{22}$}";
- $bfHash = "{^\\$2a\\$06\\$[0-9A-Za-z./]{53}$}";
-
- $xdesSalt = "{^_zD..[0-9A-Za-z./]{4}$}";
- $xdesHash = "{^_zD..[0-9A-Za-z./]{15}$}";
-
- $md5Salt = "{^\\$1\\$[0-9A-Za-z./]{8}$}";
- $md5Hash = "{^\\$1\\$[0-9A-Za-z./]{8}\\$[0-9A-Za-z./]{22}$}";
-
- // Make it a bit slow, else we'll be there tomorrow
- foreach (array('bf' => 6, 'xdes' => 10, 'md5' => false) as $method => $log2) {
- $salts = array();
- $hashes = array();
- $count = 50;
- $saltPattern = ${$method . 'Salt'};
- $hashPattern = ${$method . 'Hash'};
-
- for ($i = 0; $i < $count; $i++) {
- $salt = String::genSalt($method, $log2);
- $this->assertPattern($saltPattern, $salt);
- $this->assertFalse(in_array($salt, $salts));
- $salts[] = $salt;
-
- $hash = String::hashPassword($pass, $salt);
- $this->assertPattern($hashPattern, $hash);
- $this->assertEqual(substr($hash, 0, strlen($salt)), $salt);
- $this->assertFalse(in_array($hash, $hashes));
- $hashes[] = $hash;
-
- $this->assertTrue(String::checkPassword($pass, $hash));
- }
- }
- }
-
- /**
* testInsert method
*
* @return void
View
338 libraries/lithium/util/String.php
@@ -12,9 +12,8 @@
use Exception;
/**
- * String manipulation utility class. Includes functionality for hashing, UUID generation,
+ * String manipulation utility class. Includes functionality for generating UUIDs,
* {:tag} and regex replacement, and tokenization.
- *
*/
class String {
/**
@@ -26,83 +25,13 @@ class String {
const varRFC = 128; // 10000000 The RFC 4122 variant
/**
- * A file pointer towards urandom if available, else false
- *
- * @var resource|false
- */
- protected static $_urandom;
-
- /**
- * Seeds the random generator if it has yet to be done.
- *
- * @return boolean Success.
- */
- public static function seed() {
- // Seeding more than once means less entropy, not more, so bail
- if (isset(static::$_urandom)) {
- return false;
- }
-
- // Use urandom if the device is available
- if (is_readable('/dev/urandom')) {
- static::$_urandom = fopen('/dev/urandom', 'rb');
- // Else seed PHP's mt_rand()
- } else {
- $seed = function() {
- list($usec, $sec) = explode(' ', microtime());
- $seed = (float) $sec + ((float) $usec * 100000);
- if (function_exists('getmypid')) {
- $seed .= getmypid();
- }
- return $seed;
- };
- mt_srand($seed());
- static::$_urandom = false;
- }
-
- return true;
- }
-
- /**
- * Generates random bytes for use in UUIDs and password salts.
- *
- * The method seeds the random source automatically. It uses
- * /dev/urandom if the latter is available; `md_rand()` if not.
- *
- * It can also be used to generate arbitrary bits:
- *
- * {{{
- * $bits = bin2hex(String::random(8)); // 64 bits
- * }}}
- *
- * @param integer $bytes The number of bytes to generate
- * @param string Random bytes
- */
- public static function random($bytes) {
- if (!isset(static::$_urandom)) {
- static::seed();
- }
-
- if (static::$_urandom) {
- $rand = fread(static::$_urandom, $bytes);
- } else {
- $rand = '';
- for ($i = 0; $i < $bytes; $i++) {
- $rand .= chr(mt_rand(0, 255));
- }
- }
-
- return $rand;
- }
-
- /**
* Generates an RFC 4122-compliant version 4 UUID.
*
* @return string The string representation of an RFC 4122-compliant, version 4 UUID.
* @link http://www.ietf.org/rfc/rfc4122.txt
*/
public static function uuid() {
- $uuid = static::random(16);
+ $uuid = Crypto::random(16);
// Set version
$uuid[6] = chr(ord($uuid[6]) & static::clearVer | static::version4);
@@ -157,269 +86,6 @@ public static function hash($string, array $options = array()) {
}
/**
- * Hashes a password using PHP's `crypt()` and an optional salt. If no
- * salt is supplied, a cryptographically strong salt will be generated
- * using `String::genSalt()`.
- *
- * Using this function is the proper way to hash a password. Using naive
- * methods such as `String::hash()` is fine to check a file's integrity,
- * but fundamentally insecure for passwords, due to the invariable lack
- * of a cryptographically strong salt.
- *
- * Moreover, `String::hashPassword()`'s cryptographically strong salts
- * ensure that:
- *
- * - Two identical passwords will not be hashed the same way.
- * - `String::genSalt()`'s count interator can later be increased (assuming
- * BF or XDES is available) within Lithium or your application, without
- * invalidating existing password hashes.
- *
- * Usage:
- *
- * {{{
- * // Hash a password before storing it:
- * $hashed = String::hashPassword($password);
- *
- * // Check a password by comparing it to its hashed value:
- * $check = String::checkPassword($password, $hashed);
- *
- * // Use a stronger custom salt:
- * $salt = String::genSalt('bf', 16); // 2^16 iterations
- * $hashed = String::hashPassword($password, $salt); // Very slow
- * $check = String::checkPassword($password, $hashed); // Very slow
- *
- * // Forward/backward compatibility
- * $salt1 = String::genSalt('bf', 6);
- * $salt2 = String::genSalt('bf', 12);
- * $hashed1 = String::hashPassword($password, $salt1); // Fast
- * $hashed2 = String::hashPassword($password, $salt2); // Slow
- * $check1 = String::checkPassword($password, $hashed1); // True
- * $check2 = String::checkPassword($password, $hashed2); // True
- * }}}
- *
- * @see lithium\util\String::genSalt()
- * @param string $password The password to hash.
- * @param string $salt Optional. The salt string.
- * @return string The hashed password.
- * The result's length will be:
- * - 60 chars for Blowfish hashes
- * - 20 chars for XDES hashes
- * - 34 chars for MD5 hashes
- **/
- public static function hashPassword($password, $salt = null) {
- return crypt($password, $salt ?: static::genSalt());
- }
-
- /**
- * Compares a password and its hashed value using PHP's `crypt()`.
- *
- * @see lithium\util\String::hashPassword()
- * @see lithium\util\String::genSalt()
- * @param string $password The password to check
- * @param string $hash The hashed password to compare
- * @return boolean Whether the password is correct or not
- */
- public static function checkPassword($password, $hash) {
- return $hash == crypt($password, $hash);
- }
-
- /**
- * Generates a cryptographically strong salt, using the best available
- * method (tries Blowfish, then XDES, and fallbacks to MD5), for use in
- * `String::hashPassword()`.
- *
- * Blowfish and XDES are adaptive hashing algorithms. MD5 is not. Adaptive
- * hashing algorithms are designed in such a way that when computers get
- * faster, you can tune the algorithm to be slower by increasing the number
- * of hash iterations, without introducing incompatibility with existing
- * passwords.
- *
- * To pick an appropriate iteration count for adaptive algorithms, consider
- * that the original DES crypt was designed to have the speed of 4 hashes
- * per second on the hardware of that time. Slower than 4 hashes per second
- * would probably dampen usability. Faster than 100 hashes per second is
- * probably too fast. The defaults generate about 10 hashes per second
- * using a dual-core 2.2GHz CPU.
- *
- * Note1: this salt generator is different from naive salt implementations
- * (e.g. `md5(microtime())`) that are invariably found in OSS PHP applications,
- * in that it uses all of the available bits of entropy for the supplied salt
- * method.
- *
- * Note2: this method should not be used as custom salts, for instance in a
- * custom password hasher. Indeed, salts are prefixed with information expected
- * by PHP's `crypt()`. To get an arbitrarily long, cryptographically strong salt
- * consisting in random sequences of alpha numeric characters, combine
- * `String::random()` and `String::encode64()` instead.
- *
- * @link http://php.net/manual/en/function.crypt.php
- * @link http://www.postgresql.org/docs/9.0/static/pgcrypto.html
- * @see lithium\util\String::hashPassword()
- * @param string $type The hash type. Optional. Defaults to '`bf`'.
- * Supported values include:
- * - `'bf'`: Blowfish (128 salt bits, adaptive, max 72 chars)
- * - `'xdes'`: XDES (24 salt bits, adaptive, max 8 chars)
- * - `'md5'`: MD5 (48 salt bits, non-adaptive, unlimited length)
- * @param integer $count Optional. The base-2 logarithm of the iteration
- * count, for adaptive algorithms. Defaults to:
- * - `10` for Blowfish
- * - `18` for XDES
- * @return string The salt string.
- */
- public static function genSalt($type = null, $count = null) {
- switch (true) {
- case CRYPT_BLOWFISH == 1 && (!$type || $type === 'bf'):
- return static::_genSaltBF($count);
- case CRYPT_EXT_DES == 1 && (!$type || $type === 'xdes'):
- return static::_genSaltXDES($count);
- default:
- return static::_genSaltMD5();
- }
- }
-
- /**
- * Encodes bytes into an `./0-9A-Za-z` alphabet, for use as salt when
- * hashing passwords.
- *
- * Note: this is not the same as RFC 1421, or `base64_encode()`, which
- * uses an `+/0-9A-Za-z` alphabet.
- *
- * This function can be combined with `String::random()` to generate random
- * sequences of `./0-9A-Za-z` characters:
- *
- * {{{
- * $salt = String::encode64(String::random(8)); // 64 bits
- * }}}
- *
- * @see lithium\util\String::random()
- * @param string $input The input bytes.
- * @return string The same bytes in the `/.0-9A-Za-z` alphabet.
- */
- public static function encode64($input) {
- $base64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
- $i = 0;
-
- $count = strlen($input);
- $output = '';
-
- do {
- $value = ord($input[$i++]);
- $output .= $base64[$value & 0x3f];
-
- if ($i < $count) {
- $value |= ord($input[$i]) << 8;
- }
- $output .= $base64[($value >> 6) & 0x3f];
-
- if ($i++ >= $count) {
- break;
- }
- if ($i < $count) {
- $value |= ord($input[$i]) << 16;
- }
- $output .= $base64[($value >> 12) & 0x3f];
-
- if ($i++ >= $count) {
- break;
- }
- $output .= $base64[($value >> 18) & 0x3f];
- } while ($i < $count);
-
- return $output;
- }
-
- /**
- * Generates a Blowfish salt for use in `String::hashPassword()`.
- *
- * @param integer $count The base-2 logarithm of the iteration count.
- * Defaults to `10`. Can be `4` to `31`.
- * @return string $salt
- */
- protected static function _genSaltBf($count = 10) {
- $count = (integer) $count;
- if ($count < 4 || $count > 31) {
- $count = 10;
- }
-
- // We don't use the encode64() method here because it could result
- // in 2 bits less of entropy depending on the last char.
- $base64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
- $i = 0;
-
- $input = static::random(16); // 128 bits of salt
- $output = '';
-
- do {
- $c1 = ord($input[$i++]);
- $output .= $base64[$c1 >> 2];
- $c1 = ($c1 & 0x03) << 4;
- if ($i >= 16) {
- $output .= $base64[$c1];
- break;
- }
-
- $c2 = ord($input[$i++]);
- $c1 |= $c2 >> 4;
- $output .= $base64[$c1];
- $c1 = ($c2 & 0x0f) << 2;
-
- $c2 = ord($input[$i++]);
- $c1 |= $c2 >> 6;
- $output .= $base64[$c1];
- $output .= $base64[$c2 & 0x3f];
- } while (1);
-
- return '$2a$'
- // zeroize $count
- . chr(ord('0') + $count / 10) . chr(ord('0') + $count % 10)
- . '$' . $output;
- }
-
- /**
- * Generates an Extended DES salt for use in `String::hashPassword()`.
- *
- * @param integer $count The base-2 logarithm of the iteration count.
- * Defaults to `18`. Can be `1` to `24`. 1 will be stripped
- * from the non-log value, e.g. 2^18 - 1, to ensure we don't
- * use a weak DES key.
- * @return string The XDES salt.
- */
- protected static function _genSaltXDES($count = 18) {
- $count = (integer) $count;
- if ($count < 1 || $count > 24) {
- $count = 16;
- }
-
- // Count should be odd to not reveal weak DES keys
- $count = (1 << $count) - 1;
-
- $base64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
-
- $output = '_'
- // iterations
- . $base64[$count & 0x3f]
- . $base64[($count >> 6) & 0x3f]
- . $base64[($count >> 12) & 0x3f]
- . $base64[($count >> 18) & 0x3f]
- // 24 bits of salt
- . static::encode64(static::random(3));
-
- return $output;
- }
-
- /**
- * Generates an MD5 salt for use in `String::hashPassword()`.
- *
- * @return string The MD5 salt.
- */
- protected static function _genSaltMD5() {
- $output = '$1$'
- // 48 bits of salt
- . static::encode64(static::random(6));
- return $output;
- }
-
- /**
* Replaces variable placeholders inside a string with any given data. Each key
* in the `$data` array corresponds to a variable placeholder name in `$str`.
*
Please sign in to comment.
Something went wrong with that request. Please try again.