Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add bcrypt support to Security::hash()

  • Loading branch information...
commit 434d3a71377b52edf0af94d13e31594a785eac20 1 parent ed19821
sitedyno sitedyno authored markstory committed
56 lib/Cake/Test/Case/Utility/SecurityTest.php
View
@@ -68,6 +68,46 @@ public function testValidateAuthKey() {
}
/**
+ * testHashInvalidSalt method
+ *
+ * @expectedException PHPUnit_Framework_Error
+ * @return void
+ */
+ public function testHashInvalidSalt() {
+ $result = Security::hash('someKey', 'blowfish', true);
+ }
+
+/**
+ * testHashAnotherInvalidSalt
+ *
+ * @expectedException PHPUnit_Framework_Error
+ * @return void
+ */
+ public function testHashAnotherInvalidSalt() {
+ $result = Security::hash('someKey', 'blowfish', '$1$lksdjoijfaoijs');
+ }
+
+/**
+ * testHashYetAnotherInvalidSalt
+ *
+ * @expectedException PHPUnit_Framework_Error
+ * @return void
+ */
+ public function testHashYetAnotherInvalidSalt() {
+ $result = Security::hash('someKey', 'blowfish', '$2a$10$123');
+ }
+
+/**
+ * testHashInvalidCost method
+ *
+ * @expectedException PHPUnit_Framework_Error
+ * @return void
+ */
+ public function testHashInvalidCost() {
+ Security::setCost(1000);
+ $result = Security::hash('somekey', 'blowfish', false);
+ }
+/**
* testHash method
*
* @return void
@@ -112,6 +152,22 @@ public function testHash() {
$this->assertSame(strlen(Security::hash($key, 'sha256', true)), 64);
}
+ $hashType = 'blowfish';
+ Security::setHash($hashType);
+ Security::setCost(10); // ensure default cost
+ $this->assertSame(Security::$hashType, $hashType);
+ $this->assertSame(strlen(Security::hash($key, null, false)), 60);
+
+ $password = $submittedPassword = $key;
+ $storedPassword = Security::hash($password);
+
+ $hashedPassword = Security::hash($submittedPassword, null, $storedPassword);
+ $this->assertSame($storedPassword, $hashedPassword);
+
+ $submittedPassword = 'someOtherKey';
+ $hashedPassword = Security::hash($submittedPassword, null, $storedPassword);
+ $this->assertNotSame($storedPassword, $hashedPassword);
+
Security::setHash($_hashType);
}
111 lib/Cake/Utility/Security.php
View
@@ -34,6 +34,13 @@ class Security {
public static $hashType = null;
/**
+ * Default cost
+ *
+ * @var string
+ */
+ public static $hashCost = '10';
+
+/**
* Get allowed minutes of inactivity based on security level.
*
* @return integer Allowed inactivity in minutes
@@ -76,14 +83,26 @@ public static function validateAuthKey($authKey) {
/**
* Create a hash from string using given method.
* Fallback on next available method.
+ * If you are using blowfish, for comparisons simply pass the originally hashed
+ * string as the salt (the salt is prepended to the hash and php handles the
+ * parsing automagically. Do NOT use a constant salt for blowfish.
*
* @param string $string String to hash
* @param string $type Method to use (sha1/sha256/md5)
- * @param boolean $salt If true, automatically appends the application's salt
- * value to $string (Security.salt)
+ * @param mixed $salt If true, automatically appends the application's salt
+ * value to $string (Security.salt). If you are using blowfish the salt
+ * must be false or a previously generated salt.
* @return string Hash
*/
public static function hash($string, $type = null, $salt = false) {
+ if (empty($type)) {
+ $type = self::$hashType;
+ }
+ $type = strtolower($type);
+
+ if ($type === 'blowfish') {
+ return self::_crypt($string, $type, $salt);
+ }
if ($salt) {
if (is_string($salt)) {
$string = $salt . $string;
@@ -92,11 +111,6 @@ public static function hash($string, $type = null, $salt = false) {
}
}
- if (empty($type)) {
- $type = self::$hashType;
- }
- $type = strtolower($type);
-
if ($type == 'sha1' || $type == null) {
if (function_exists('sha1')) {
$return = sha1($string);
@@ -119,7 +133,7 @@ public static function hash($string, $type = null, $salt = false) {
* Sets the default hash method for the Security object. This affects all objects using
* Security::hash().
*
- * @param string $hash Method to use (sha1/sha256/md5)
+ * @param string $hash Method to use (sha1/sha256/md5/blowfish)
* @return void
* @see Security::hash()
*/
@@ -128,6 +142,16 @@ public static function setHash($hash) {
}
/**
+ * Sets the cost for they blowfish hash method.
+ *
+ * @param integer $cost Valid values are 4-31
+ * @return void
+ */
+ public static function setCost($cost) {
+ self::$hashCost = $cost;
+ }
+
+/**
* Encrypts/Decrypts a text using the given key.
*
* @param string $text Encrypted string to decrypt, normal string to encrypt
@@ -189,4 +213,75 @@ public static function rijndael($text, $key, $operation) {
return $out;
}
+/**
+ * Generates a pseudo random salt suitable for use with php's crypt() function.
+ * The salt length should not exceed 27. The salt will be composed of
+ * [./0-9A-Za-z]{$length}.
+ *
+ * @param integer $length The length of the returned salt
+ * @return string The generated salt
+ */
+ public static function salt($length = 22) {
+ return substr(str_replace('+', '.', base64_encode(sha1(uniqid(Configure::read('Security.salt'), true), true))), 0, $length);
Thiago Belem
TiuTalk added a note

What about the == or = padding from base64? http://en.wikipedia.org/wiki/Base64#Padding

The = isn't an allowed character to the blowfish salt.

Mark Story Owner

Good point, I'll update that.

Mark Story Owner

Fixed in 24b68ec

The original generates salts up to 27 characters w/o any "=" (this is specified in the comments). The blowfish salt is 22 characters and all the other salts used by crypt are shorter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ }
+
+/**
+ * One way encryption using php's crypt() function.
+ *
+ * @param string $password The string to be encrypted.
+ * @param string $type The encryption method to use (blowfish)
+ * @param mixed $salt false to generate a new salt or an existing salt.
+ */
+ protected static function _crypt($password, $type = null, $salt = false) {
+ $options = array(
+ 'saltFormat' => array(
+ 'blowfish' => '$2a$%s$%s',
+ ),
+ 'saltLength' => array(
+ 'blowfish' => 22,
+ ),
+ 'costLimits' => array(
+ 'blowfish' => array(4, 31),
+ )
+ );
+ extract($options);
+ if ($type === null) {
+ $hashType = self::$hashType;
+ } else {
+ $hashType = $type;
+ }
+ $cost = self::$hashCost;
+ if ($salt === false) {
+ if (isset($costLimits[$hashType]) && ($cost < $costLimits[$hashType][0] || $cost > $costLimits[$hashType][1])) {
+ trigger_error(__d(
+ 'cake_dev',
+ 'When using %s you must specify a cost between %s and %s',
+ array(
+ $hashType,
+ $costLimits[$hashType][0],
+ $costLimits[$hashType][1]
+ )
+ ), E_USER_WARNING);
+ return '';
+ }
+ $vspArgs = array();
+ $salt = self::salt($saltLength[$hashType]);
+ if ($hashType === 'blowfish') {
+ $bfCost = chr(ord('0') + $cost / 10);
+ $bfCost .= chr(ord('0') + $cost % 10);
+ $vspArgs[] = $bfCost;
+ }
+ $vspArgs[] = $salt;
+ $salt = vsprintf($saltFormat[$hashType], $vspArgs);
+ } elseif ($salt === true || strpos($salt, '$2a$') !== 0 || strlen($salt) < 29) {
+ trigger_error(__d(
+ 'cake_dev',
+ 'Invalid salt: %s for %s Please visit http://www.php.net/crypt and read the appropriate section for building %s salts.',
+ array($salt, $hashType, $hashType)
+ ), E_USER_WARNING);
+ return '';
+ }
+ return crypt($password, $salt);
+ }
+
}
Thiago Belem

What about the == or = padding from base64? http://en.wikipedia.org/wiki/Base64#Padding

The = isn't an allowed character to the blowfish salt.

Mark Story

Good point, I'll update that.

sitedyno

The original generates salts up to 27 characters w/o any "=" (this is specified in the comments). The blowfish salt is 22 characters and all the other salts used by crypt are shorter.

Please sign in to comment.
Something went wrong with that request. Please try again.