diff options
-rw-r--r-- | CHANGELOG.md | 5 | ||||
-rw-r--r-- | Core/Encoder/Pbkdf2PasswordEncoder.php | 97 | ||||
-rw-r--r-- | Http/Firewall/UsernamePasswordFormAuthenticationListener.php | 2 | ||||
-rw-r--r-- | Tests/Core/Encoder/Pbkdf2PasswordEncoderTest.php | 45 | ||||
-rw-r--r-- | composer.json | 26 |
5 files changed, 161 insertions, 14 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index c570ac3..b1c8192 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +2.2.0 +----- + +* Added PBKDF2 Password encoder + 2.1.0 ----- diff --git a/Core/Encoder/Pbkdf2PasswordEncoder.php b/Core/Encoder/Pbkdf2PasswordEncoder.php new file mode 100644 index 0000000..656545f --- /dev/null +++ b/Core/Encoder/Pbkdf2PasswordEncoder.php @@ -0,0 +1,97 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Encoder; + +/** + * Pbkdf2PasswordEncoder uses the PBKDF2 (Password-Based Key Derivation Function 2). + * + * Providing a high level of Cryptographic security, + * PBKDF2 is recommended by the National Institute of Standards and Technology (NIST). + * + * But also warrants a warning, using PBKDF2 (with a high number of iterations) slows down the process. + * PBKDF2 should be used with caution and care. + * + * @author Sebastiaan Stok <s.stok@rollerscapes.net> + * @author Andrew Johnson + * @author Fabien Potencier <fabien@symfony.com> + */ +class Pbkdf2PasswordEncoder extends BasePasswordEncoder +{ + private $algorithm; + private $encodeHashAsBase64; + private $iterations; + private $length; + + /** + * Constructor. + * + * @param string $algorithm The digest algorithm to use + * @param Boolean $encodeHashAsBase64 Whether to base64 encode the password hash + * @param integer $iterations The number of iterations to use to stretch the password hash + * @param integer $length Length of derived key to create + */ + public function __construct($algorithm = 'sha512', $encodeHashAsBase64 = true, $iterations = 1000, $length = 40) + { + $this->algorithm = $algorithm; + $this->encodeHashAsBase64 = $encodeHashAsBase64; + $this->iterations = $iterations; + $this->length = $length; + } + + /** + * {@inheritdoc} + * + * @throws \LogicException when the algorithm is not supported + */ + public function encodePassword($raw, $salt) + { + if (!in_array($this->algorithm, hash_algos(), true)) { + throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm)); + } + + if (function_exists('hash_pbkdf2')) { + $digest = hash_pbkdf2($this->algorithm, $raw, $salt, $this->iterations, $this->length, true); + } else { + $digest = $this->hashPbkdf2($this->algorithm, $raw, $salt, $this->iterations, $this->length); + } + + return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest); + } + + /** + * {@inheritdoc} + */ + public function isPasswordValid($encoded, $raw, $salt) + { + return $this->comparePasswords($encoded, $this->encodePassword($raw, $salt)); + } + + private function hashPbkdf2($algorithm, $password, $salt, $iterations, $length = 0) + { + // Number of blocks needed to create the derived key + $blocks = ceil($length / strlen(hash($algorithm, null, true))); + $digest = ''; + + for ($i = 1; $i <= $blocks; $i++) { + $ib = $block = hash_hmac($algorithm, $salt . pack('N', $i), $password, true); + + // Iterations + for ($j = 1; $j < $iterations; $j++) { + $ib ^= ($block = hash_hmac($algorithm, $block, $password, true)); + } + + $digest .= $ib; + } + + return substr($digest, 0, $this->length); + } +} diff --git a/Http/Firewall/UsernamePasswordFormAuthenticationListener.php b/Http/Firewall/UsernamePasswordFormAuthenticationListener.php index 22330a8..057ff71 100644 --- a/Http/Firewall/UsernamePasswordFormAuthenticationListener.php +++ b/Http/Firewall/UsernamePasswordFormAuthenticationListener.php @@ -67,7 +67,7 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL */ protected function attemptAuthentication(Request $request) { - if ($this->options['post_only'] && 'post' !== strtolower($request->getMethod())) { + if ($this->options['post_only'] && !$request->isMethod('post')) { if (null !== $this->logger) { $this->logger->debug(sprintf('Authentication method not supported: %s.', $request->getMethod())); } diff --git a/Tests/Core/Encoder/Pbkdf2PasswordEncoderTest.php b/Tests/Core/Encoder/Pbkdf2PasswordEncoderTest.php new file mode 100644 index 0000000..2c98543 --- /dev/null +++ b/Tests/Core/Encoder/Pbkdf2PasswordEncoderTest.php @@ -0,0 +1,45 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Tests\Core\Encoder; + +use Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder; + +class Pbkdf2PasswordEncoderTest extends \PHPUnit_Framework_TestCase +{ + public function testIsPasswordValid() + { + $encoder = new Pbkdf2PasswordEncoder('sha256', false, 1, 40); + + $this->assertTrue($encoder->isPasswordValid('c1232f10f62715fda06ae7c0a2037ca19b33cf103b727ba56d870c11f290a2ab106974c75607c8a3', 'password', '')); + } + + public function testEncodePassword() + { + $encoder = new Pbkdf2PasswordEncoder('sha256', false, 1, 40); + $this->assertSame('c1232f10f62715fda06ae7c0a2037ca19b33cf103b727ba56d870c11f290a2ab106974c75607c8a3', $encoder->encodePassword('password', '')); + + $encoder = new Pbkdf2PasswordEncoder('sha256', true, 1, 40); + $this->assertSame('wSMvEPYnFf2gaufAogN8oZszzxA7cnulbYcMEfKQoqsQaXTHVgfIow==', $encoder->encodePassword('password', '')); + + $encoder = new Pbkdf2PasswordEncoder('sha256', false, 2, 40); + $this->assertSame('8bc2f9167a81cdcfad1235cd9047f1136271c1f978fcfcb35e22dbeafa4634f6fd2214218ed63ebb', $encoder->encodePassword('password', '')); + } + + /** + * @expectedException LogicException + */ + public function testEncodePasswordAlgorithmDoesNotExist() + { + $encoder = new Pbkdf2PasswordEncoder('foobar'); + $encoder->encodePassword('password', ''); + } +} diff --git a/composer.json b/composer.json index 0cf0a30..73e07b5 100644 --- a/composer.json +++ b/composer.json @@ -17,33 +17,33 @@ ], "require": { "php": ">=5.3.3", - "symfony/event-dispatcher": "2.1.*", - "symfony/http-foundation": "2.1.*", - "symfony/http-kernel": "2.1.*" + "symfony/event-dispatcher": "2.2.*", + "symfony/http-foundation": "2.2.*", + "symfony/http-kernel": "2.2.*" }, "require-dev": { - "symfony/form": "2.1.*", - "symfony/routing": "2.1.*", - "symfony/validator": "2.1.*", + "symfony/form": "2.2.*", + "symfony/routing": "2.2.*", + "symfony/validator": "2.2.*", "doctrine/common": ">=2.2,<2.4-dev", "doctrine/dbal": ">=2.2,<2.4-dev" }, "suggest": { - "symfony/class-loader": "2.1.*", - "symfony/finder": "2.1.*", - "symfony/form": "2.1.*", - "symfony/validator": "2.1.*", - "symfony/routing": "2.1.*", + "symfony/class-loader": "2.2.*", + "symfony/finder": "2.2.*", + "symfony/form": "2.2.*", + "symfony/validator": "2.2.*", + "symfony/routing": "2.2.*", "doctrine/dbal": "to use the built-in ACL implementation" }, "autoload": { - "psr-0": { "Symfony\\Component\\Security": "" } + "psr-0": { "Symfony\\Component\\Security\\": "" } }, "target-dir": "Symfony/Component/Security", "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.2-dev" } } } |