summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorElnur Abdurrakhimov <elnur@elnur.pro>2013-03-03 15:51:00 +0400
committerFabien Potencier <fabien.potencier@gmail.com>2013-04-25 17:38:27 +0200
commitf7a0e499cf233a7a66c7f2fbfbd98504171f9603 (patch)
tree38df2668b64a91dfebde987a24bf4c2119c46b01
parentd156097a1b4653108637dedaaf2c64b876eb2f35 (diff)
downloadsymfony-security-f7a0e499cf233a7a66c7f2fbfbd98504171f9603.zip
symfony-security-f7a0e499cf233a7a66c7f2fbfbd98504171f9603.tar.gz
symfony-security-f7a0e499cf233a7a66c7f2fbfbd98504171f9603.tar.bz2
Outsource all the BCrypt heavy lifting to a library
-rw-r--r--Core/Encoder/BCryptPasswordEncoder.php101
-rw-r--r--Tests/Core/Encoder/BCryptPasswordEncoderTest.php60
-rw-r--r--composer.json3
3 files changed, 14 insertions, 150 deletions
diff --git a/Core/Encoder/BCryptPasswordEncoder.php b/Core/Encoder/BCryptPasswordEncoder.php
index 1b7572d..3a9d03e 100644
--- a/Core/Encoder/BCryptPasswordEncoder.php
+++ b/Core/Encoder/BCryptPasswordEncoder.php
@@ -12,7 +12,6 @@
namespace Symfony\Component\Security\Core\Encoder;
use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;
-use Symfony\Component\Security\Core\Util\SecureRandomInterface;
/**
* @author Elnur Abdurrakhimov <elnur@elnur.pro>
@@ -21,38 +20,25 @@ use Symfony\Component\Security\Core\Util\SecureRandomInterface;
class BCryptPasswordEncoder extends BasePasswordEncoder
{
/**
- * @var SecureRandomInterface
- */
- private $secureRandom;
-
- /**
* @var string
*/
private $cost;
- private static $prefix = null;
-
/**
* Constructor.
*
- * @param SecureRandomInterface $secureRandom A SecureRandomInterface instance
- * @param integer $cost The algorithmic cost that should be used
+ * @param integer $cost The algorithmic cost that should be used
*
* @throws \InvalidArgumentException if cost is out of range
*/
- public function __construct(SecureRandomInterface $secureRandom, $cost)
+ public function __construct($cost)
{
- $this->secureRandom = $secureRandom;
-
$cost = (int) $cost;
if ($cost < 4 || $cost > 31) {
throw new \InvalidArgumentException('Cost must be in the range of 4-31.');
}
- $this->cost = sprintf('%02d', $cost);
- if (!self::$prefix) {
- self::$prefix = '$'.(version_compare(phpversion(), '5.3.7', '>=') ? '2y' : '2a').'$';
- }
+ $this->cost = sprintf('%02d', $cost);
}
/**
@@ -60,17 +46,9 @@ class BCryptPasswordEncoder extends BasePasswordEncoder
*/
public function encodePassword($raw, $salt)
{
- if (function_exists('password_hash')) {
- return password_hash($raw, PASSWORD_BCRYPT, array('cost' => $this->cost));
- }
-
- $salt = self::$prefix.$this->cost.'$'.$this->encodeSalt($this->getRawSalt());
- $encoded = crypt($raw, $salt);
- if (!is_string($encoded) || strlen($encoded) <= 13) {
- return false;
- }
-
- return $encoded;
+ return password_hash($raw, PASSWORD_BCRYPT, array(
+ 'cost' => $this->cost,
+ ));
}
/**
@@ -78,71 +56,6 @@ class BCryptPasswordEncoder extends BasePasswordEncoder
*/
public function isPasswordValid($encoded, $raw, $salt)
{
- if (function_exists('password_verify')) {
- return password_verify($raw, $encoded);
- }
-
- $crypted = crypt($raw, $encoded);
- if (strlen($crypted) <= 13) {
- return false;
- }
-
- return $this->comparePasswords($encoded, $crypted);
- }
-
- /**
- * Encodes the salt to be used by Bcrypt.
- *
- * The blowfish/bcrypt algorithm used by PHP crypt expects a different
- * set and order of characters than the usual base64_encode function.
- * Regular b64: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
- * Bcrypt b64: ./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
- * We care because the last character in our encoded string will
- * only represent 2 bits. While two known implementations of
- * bcrypt will happily accept and correct a salt string which
- * has the 4 unused bits set to non-zero, we do not want to take
- * chances and we also do not want to waste an additional byte
- * of entropy.
- *
- * @param bytes $random a string of 16 random bytes
- *
- * @return string Properly encoded salt to use with php crypt function
- *
- * @throws \InvalidArgumentException if string of random bytes is too short
- */
- protected function encodeSalt($random)
- {
- $len = strlen($random);
- if ($len < 16) {
- throw new \InvalidArgumentException('The bcrypt salt needs 16 random bytes.');
- }
- if ($len > 16) {
- $random = substr($random, 0, 16);
- }
-
- $base64raw = str_replace('+', '.', base64_encode($random));
- $salt128bit = substr($base64raw, 0, 21);
- $lastchar = substr($base64raw, 21, 1);
- $lastchar = strtr($lastchar, 'AQgw', '.Oeu');
- $salt128bit .= $lastchar;
-
- return $salt128bit;
- }
-
- /**
- * @return bytes 16 random bytes to be used in the salt
- */
- protected function getRawSalt()
- {
- $rawSalt = false;
- $numBytes = 16;
- if (function_exists('mcrypt_create_iv')) {
- $rawSalt = mcrypt_create_iv($numBytes, MCRYPT_DEV_URANDOM);
- }
- if (!$rawSalt) {
- $rawSalt = $this->secureRandom->nextBytes($numBytes);
- }
-
- return $rawSalt;
+ return password_verify($raw, $encoded);
}
}
diff --git a/Tests/Core/Encoder/BCryptPasswordEncoderTest.php b/Tests/Core/Encoder/BCryptPasswordEncoderTest.php
index 45c8f74..6378433 100644
--- a/Tests/Core/Encoder/BCryptPasswordEncoderTest.php
+++ b/Tests/Core/Encoder/BCryptPasswordEncoderTest.php
@@ -22,30 +22,12 @@ class BCryptPasswordEncoderTest extends \PHPUnit_Framework_TestCase
const BYTES = '0123456789abcdef';
const VALID_COST = '04';
- const SECURE_RANDOM_INTERFACE = 'Symfony\Component\Security\Core\Util\SecureRandomInterface';
-
- /**
- * @var \PHPUnit_Framework_MockObject_MockObject
- */
- private $secureRandom;
-
- protected function setUp()
- {
- $this->secureRandom = $this->getMock(self::SECURE_RANDOM_INTERFACE);
-
- $this->secureRandom
- ->expects($this->any())
- ->method('nextBytes')
- ->will($this->returnValue(self::BYTES))
- ;
- }
-
/**
* @expectedException \InvalidArgumentException
*/
public function testCostBelowRange()
{
- new BCryptPasswordEncoder($this->secureRandom, 3);
+ new BCryptPasswordEncoder(3);
}
/**
@@ -53,60 +35,28 @@ class BCryptPasswordEncoderTest extends \PHPUnit_Framework_TestCase
*/
public function testCostAboveRange()
{
- new BCryptPasswordEncoder($this->secureRandom, 32);
+ new BCryptPasswordEncoder(32);
}
public function testCostInRange()
{
for ($cost = 4; $cost <= 31; $cost++) {
- new BCryptPasswordEncoder($this->secureRandom, $cost);
+ new BCryptPasswordEncoder($cost);
}
}
public function testResultLength()
{
- $encoder = new BCryptPasswordEncoder($this->secureRandom, self::VALID_COST);
+ $encoder = new BCryptPasswordEncoder(self::VALID_COST);
$result = $encoder->encodePassword(self::PASSWORD, null);
$this->assertEquals(60, strlen($result));
}
public function testValidation()
{
- $encoder = new BCryptPasswordEncoder($this->secureRandom, self::VALID_COST);
+ $encoder = new BCryptPasswordEncoder(self::VALID_COST);
$result = $encoder->encodePassword(self::PASSWORD, null);
$this->assertTrue($encoder->isPasswordValid($result, self::PASSWORD, null));
$this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null));
}
-
- public function testValidationKnownPassword()
- {
- $encoder = new BCryptPasswordEncoder($this->secureRandom, self::VALID_COST);
- $prefix = '$'.(version_compare(phpversion(), '5.3.7', '>=')
- ? '2y' : '2a').'$';
-
- $encrypted = $prefix.'04$ABCDEFGHIJKLMNOPQRSTU.uTmwd4KMSHxbUsG7bng8x7YdA0PM1iq';
- $this->assertTrue($encoder->isPasswordValid($encrypted, self::PASSWORD, null));
- }
-
- public function testSecureRandomIsUsed()
- {
- if (function_exists('mcrypt_create_iv')) {
- return;
- }
-
- $this->secureRandom
- ->expects($this->atLeastOnce())
- ->method('nextBytes')
- ;
-
- $encoder = new BCryptPasswordEncoder($this->secureRandom, self::VALID_COST);
- $result = $encoder->encodePassword(self::PASSWORD, null);
-
- $prefix = '$'.(version_compare(phpversion(), '5.3.7', '>=')
- ? '2y' : '2a').'$';
- $salt = 'MDEyMzQ1Njc4OWFiY2RlZe';
- $expected = crypt(self::PASSWORD, $prefix.self::VALID_COST.'$'.$salt);
-
- $this->assertEquals($expected, $result);
- }
}
diff --git a/composer.json b/composer.json
index dd4eecf..c66949c 100644
--- a/composer.json
+++ b/composer.json
@@ -19,7 +19,8 @@
"php": ">=5.3.3",
"symfony/event-dispatcher": "~2.1",
"symfony/http-foundation": ">=2.1,<2.4-dev",
- "symfony/http-kernel": ">=2.1,<=2.3-dev"
+ "symfony/http-kernel": ">=2.1,<=2.3-dev",
+ "ircmaxell/password-compat": "1.0.*"
},
"require-dev": {
"symfony/form": "~2.0",