Commit 4ce4707a by Paul Klimov

Option `Security::passwordHashStrategy` added

parent 40635024
...@@ -58,10 +58,18 @@ class Security extends Component ...@@ -58,10 +58,18 @@ class Security extends Component
/** /**
* @var string strategy, which should be used to derive a key for encryption. * @var string strategy, which should be used to derive a key for encryption.
* Available strategies: * Available strategies:
* - 'pbkdf2' - PBKDF2 key derivation, this option is recommended, but it requires PHP version >= 5.5.0 * - 'pbkdf2' - PBKDF2 key derivation. This option is recommended, but it requires PHP version >= 5.5.0
* - 'hmac' - HMAC hash key derivation. * - 'hmac' - HMAC hash key derivation.
*/ */
public $deriveKeyStrategy = 'hmac'; public $deriveKeyStrategy = 'hmac';
/**
* @var string strategy, which should be used to generate password hash.
* Available strategies:
* - 'password_hash' - use of PHP `password_hash()` function with PASSWORD_DEFAULT algorithm. This option is recommended,
* but it requires PHP version >= 5.5.0
* - 'crypt' - use PHP `crypt()` function.
*/
public $passwordHashStrategy = 'crypt';
/** /**
* Encrypts data. * Encrypts data.
...@@ -154,7 +162,7 @@ class Security extends Component ...@@ -154,7 +162,7 @@ class Security extends Component
case 'hmac': case 'hmac':
return $this->deriveKeyHmac($password, $salt); return $this->deriveKeyHmac($password, $salt);
default: default:
throw new InvalidConfigException("Unknown Derive key strategy '{$this->deriveKeyStrategy}'"); throw new InvalidConfigException("Unknown derive key strategy '{$this->deriveKeyStrategy}'");
} }
} }
...@@ -335,14 +343,23 @@ class Security extends Component ...@@ -335,14 +343,23 @@ class Security extends Component
*/ */
public function generatePasswordHash($password, $cost = 13) public function generatePasswordHash($password, $cost = 13)
{ {
$salt = $this->generateSalt($cost); switch ($this->passwordHashStrategy) {
$hash = crypt($password, $salt); case 'password_hash':
if (!function_exists('password_hash')) {
if (!is_string($hash) || strlen($hash) < 32) { throw new InvalidConfigException('Password hash key strategy "password_hash" requires PHP >= 5.5.0, either upgrade your environment or use another strategy.');
throw new Exception('Unknown error occurred while generating hash.'); }
return password_hash($password, PASSWORD_DEFAULT, ['cost' => $cost]);
case 'crypt':
$salt = $this->generateSalt($cost);
$hash = crypt($password, $salt);
if (!is_string($hash) || strlen($hash) < 32) {
throw new Exception('Unknown error occurred while generating hash.');
}
return $hash;
default:
throw new InvalidConfigException("Unknown password hash strategy '{$this->passwordHashStrategy}'");
} }
return $hash;
} }
/** /**
...@@ -351,6 +368,7 @@ class Security extends Component ...@@ -351,6 +368,7 @@ class Security extends Component
* @param string $hash The hash to verify the password against. * @param string $hash The hash to verify the password against.
* @return boolean whether the password is correct. * @return boolean whether the password is correct.
* @throws InvalidParamException on bad password or hash parameters or if crypt() with Blowfish hash is not available. * @throws InvalidParamException on bad password or hash parameters or if crypt() with Blowfish hash is not available.
* @throws InvalidConfigException on unsupported password hash strategy is configured.
* @see generatePasswordHash() * @see generatePasswordHash()
*/ */
public function validatePassword($password, $hash) public function validatePassword($password, $hash)
...@@ -363,13 +381,22 @@ class Security extends Component ...@@ -363,13 +381,22 @@ class Security extends Component
throw new InvalidParamException('Hash is invalid.'); throw new InvalidParamException('Hash is invalid.');
} }
$test = crypt($password, $hash); switch ($this->passwordHashStrategy) {
$n = strlen($test); case 'password_hash':
if ($n < 32 || $n !== strlen($hash)) { if (!function_exists('password_verify')) {
return false; throw new InvalidConfigException('Password hash key strategy "password_hash" requires PHP >= 5.5.0, either upgrade your environment or use another strategy.');
}
return password_verify($password, $hash);
case 'crypt':
$test = crypt($password, $hash);
$n = strlen($test);
if ($n < 32 || $n !== strlen($hash)) {
return false;
}
return $this->compareString($test, $hash);
default:
throw new InvalidConfigException("Unknown password hash strategy '{$this->passwordHashStrategy}'");
} }
return $this->compareString($test, $hash);
} }
/** /**
......
...@@ -29,14 +29,6 @@ class SecurityTest extends TestCase ...@@ -29,14 +29,6 @@ class SecurityTest extends TestCase
// Tests : // Tests :
public function testPasswordHash()
{
$password = 'secret';
$hash = $this->security->generatePasswordHash($password);
$this->assertTrue($this->security->validatePassword($password, $hash));
$this->assertFalse($this->security->validatePassword('test', $hash));
}
public function testHashData() public function testHashData()
{ {
$data = 'known data'; $data = 'known data';
...@@ -48,6 +40,40 @@ class SecurityTest extends TestCase ...@@ -48,6 +40,40 @@ class SecurityTest extends TestCase
$this->assertFalse($this->security->validateData($hashedData, $key)); $this->assertFalse($this->security->validateData($hashedData, $key));
} }
public function dataProviderPasswordHash()
{
return [
[
'crypt',
false
],
[
'password_hash',
!function_exists('password_hash')
],
];
}
/**
* @dataProvider dataProviderPasswordHash
*
* @param string $passwordHashStrategy
* @param boolean $isSkipped
*/
public function testPasswordHash($passwordHashStrategy, $isSkipped)
{
if ($isSkipped) {
$this->markTestSkipped("Unable to test '{$passwordHashStrategy}' password hash strategy");
return;
}
$this->security->passwordHashStrategy = $passwordHashStrategy;
$password = 'secret';
$hash = $this->security->generatePasswordHash($password);
$this->assertTrue($this->security->validatePassword($password, $hash));
$this->assertFalse($this->security->validatePassword('test', $hash));
}
/** /**
* Data provider for [[testEncrypt()]] * Data provider for [[testEncrypt()]]
* @return array test data * @return array test data
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment