Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b2e553a

Browse files
elnurfabpot
authored andcommitted
Outsource all the BCrypt heavy lifting to a library
1 parent 1099858 commit b2e553a

File tree

4 files changed

+16
-151
lines changed

4 files changed

+16
-151
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"symfony/icu": "~1.0",
2121
"doctrine/common": "~2.2",
2222
"twig/twig": "~1.11",
23-
"psr/log": "~1.0"
23+
"psr/log": "~1.0",
24+
"ircmaxell/password-compat": "1.0.*"
2425
},
2526
"replace": {
2627
"symfony/browser-kit": "self.version",

src/Symfony/Component/Security/Core/Encoder/BCryptPasswordEncoder.php

Lines changed: 7 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -12,137 +12,50 @@
1212
namespace Symfony\Component\Security\Core\Encoder;
1313

1414
use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;
15-
use Symfony\Component\Security\Core\Util\SecureRandomInterface;
1615

1716
/**
1817
* @author Elnur Abdurrakhimov <[email protected]>
1918
* @author Terje Bråten <[email protected]>
2019
*/
2120
class BCryptPasswordEncoder extends BasePasswordEncoder
2221
{
23-
/**
24-
* @var SecureRandomInterface
25-
*/
26-
private $secureRandom;
27-
2822
/**
2923
* @var string
3024
*/
3125
private $cost;
3226

33-
private static $prefix = null;
34-
3527
/**
3628
* Constructor.
3729
*
38-
* @param SecureRandomInterface $secureRandom A SecureRandomInterface instance
39-
* @param integer $cost The algorithmic cost that should be used
30+
* @param integer $cost The algorithmic cost that should be used
4031
*
4132
* @throws \InvalidArgumentException if cost is out of range
4233
*/
43-
public function __construct(SecureRandomInterface $secureRandom, $cost)
34+
public function __construct($cost)
4435
{
45-
$this->secureRandom = $secureRandom;
46-
4736
$cost = (int) $cost;
4837
if ($cost < 4 || $cost > 31) {
4938
throw new \InvalidArgumentException('Cost must be in the range of 4-31.');
5039
}
51-
$this->cost = sprintf('%02d', $cost);
5240

53-
if (!self::$prefix) {
54-
self::$prefix = '$'.(version_compare(phpversion(), '5.3.7', '>=') ? '2y' : '2a').'$';
55-
}
41+
$this->cost = sprintf('%02d', $cost);
5642
}
5743

5844
/**
5945
* {@inheritdoc}
6046
*/
6147
public function encodePassword($raw, $salt)
6248
{
63-
if (function_exists('password_hash')) {
64-
return password_hash($raw, PASSWORD_BCRYPT, array('cost' => $this->cost));
65-
}
66-
67-
$salt = self::$prefix.$this->cost.'$'.$this->encodeSalt($this->getRawSalt());
68-
$encoded = crypt($raw, $salt);
69-
if (!is_string($encoded) || strlen($encoded) <= 13) {
70-
return false;
71-
}
72-
73-
return $encoded;
49+
return password_hash($raw, PASSWORD_BCRYPT, array(
50+
'cost' => $this->cost,
51+
));
7452
}
7553

7654
/**
7755
* {@inheritdoc}
7856
*/
7957
public function isPasswordValid($encoded, $raw, $salt)
8058
{
81-
if (function_exists('password_verify')) {
82-
return password_verify($raw, $encoded);
83-
}
84-
85-
$crypted = crypt($raw, $encoded);
86-
if (strlen($crypted) <= 13) {
87-
return false;
88-
}
89-
90-
return $this->comparePasswords($encoded, $crypted);
91-
}
92-
93-
/**
94-
* Encodes the salt to be used by Bcrypt.
95-
*
96-
* The blowfish/bcrypt algorithm used by PHP crypt expects a different
97-
* set and order of characters than the usual base64_encode function.
98-
* Regular b64: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
99-
* Bcrypt b64: ./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
100-
* We care because the last character in our encoded string will
101-
* only represent 2 bits. While two known implementations of
102-
* bcrypt will happily accept and correct a salt string which
103-
* has the 4 unused bits set to non-zero, we do not want to take
104-
* chances and we also do not want to waste an additional byte
105-
* of entropy.
106-
*
107-
* @param bytes $random a string of 16 random bytes
108-
*
109-
* @return string Properly encoded salt to use with php crypt function
110-
*
111-
* @throws \InvalidArgumentException if string of random bytes is too short
112-
*/
113-
protected function encodeSalt($random)
114-
{
115-
$len = strlen($random);
116-
if ($len < 16) {
117-
throw new \InvalidArgumentException('The bcrypt salt needs 16 random bytes.');
118-
}
119-
if ($len > 16) {
120-
$random = substr($random, 0, 16);
121-
}
122-
123-
$base64raw = str_replace('+', '.', base64_encode($random));
124-
$salt128bit = substr($base64raw, 0, 21);
125-
$lastchar = substr($base64raw, 21, 1);
126-
$lastchar = strtr($lastchar, 'AQgw', '.Oeu');
127-
$salt128bit .= $lastchar;
128-
129-
return $salt128bit;
130-
}
131-
132-
/**
133-
* @return bytes 16 random bytes to be used in the salt
134-
*/
135-
protected function getRawSalt()
136-
{
137-
$rawSalt = false;
138-
$numBytes = 16;
139-
if (function_exists('mcrypt_create_iv')) {
140-
$rawSalt = mcrypt_create_iv($numBytes, MCRYPT_DEV_URANDOM);
141-
}
142-
if (!$rawSalt) {
143-
$rawSalt = $this->secureRandom->nextBytes($numBytes);
144-
}
145-
146-
return $rawSalt;
59+
return password_verify($raw, $encoded);
14760
}
14861
}

src/Symfony/Component/Security/Tests/Core/Encoder/BCryptPasswordEncoderTest.php

Lines changed: 5 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -22,91 +22,41 @@ class BCryptPasswordEncoderTest extends \PHPUnit_Framework_TestCase
2222
const BYTES = '0123456789abcdef';
2323
const VALID_COST = '04';
2424

25-
const SECURE_RANDOM_INTERFACE = 'Symfony\Component\Security\Core\Util\SecureRandomInterface';
26-
27-
/**
28-
* @var \PHPUnit_Framework_MockObject_MockObject
29-
*/
30-
private $secureRandom;
31-
32-
protected function setUp()
33-
{
34-
$this->secureRandom = $this->getMock(self::SECURE_RANDOM_INTERFACE);
35-
36-
$this->secureRandom
37-
->expects($this->any())
38-
->method('nextBytes')
39-
->will($this->returnValue(self::BYTES))
40-
;
41-
}
42-
4325
/**
4426
* @expectedException \InvalidArgumentException
4527
*/
4628
public function testCostBelowRange()
4729
{
48-
new BCryptPasswordEncoder($this->secureRandom, 3);
30+
new BCryptPasswordEncoder(3);
4931
}
5032

5133
/**
5234
* @expectedException \InvalidArgumentException
5335
*/
5436
public function testCostAboveRange()
5537
{
56-
new BCryptPasswordEncoder($this->secureRandom, 32);
38+
new BCryptPasswordEncoder(32);
5739
}
5840

5941
public function testCostInRange()
6042
{
6143
for ($cost = 4; $cost <= 31; $cost++) {
62-
new BCryptPasswordEncoder($this->secureRandom, $cost);
44+
new BCryptPasswordEncoder($cost);
6345
}
6446
}
6547

6648
public function testResultLength()
6749
{
68-
$encoder = new BCryptPasswordEncoder($this->secureRandom, self::VALID_COST);
50+
$encoder = new BCryptPasswordEncoder(self::VALID_COST);
6951
$result = $encoder->encodePassword(self::PASSWORD, null);
7052
$this->assertEquals(60, strlen($result));
7153
}
7254

7355
public function testValidation()
7456
{
75-
$encoder = new BCryptPasswordEncoder($this->secureRandom, self::VALID_COST);
57+
$encoder = new BCryptPasswordEncoder(self::VALID_COST);
7658
$result = $encoder->encodePassword(self::PASSWORD, null);
7759
$this->assertTrue($encoder->isPasswordValid($result, self::PASSWORD, null));
7860
$this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null));
7961
}
80-
81-
public function testValidationKnownPassword()
82-
{
83-
$encoder = new BCryptPasswordEncoder($this->secureRandom, self::VALID_COST);
84-
$prefix = '$'.(version_compare(phpversion(), '5.3.7', '>=')
85-
? '2y' : '2a').'$';
86-
87-
$encrypted = $prefix.'04$ABCDEFGHIJKLMNOPQRSTU.uTmwd4KMSHxbUsG7bng8x7YdA0PM1iq';
88-
$this->assertTrue($encoder->isPasswordValid($encrypted, self::PASSWORD, null));
89-
}
90-
91-
public function testSecureRandomIsUsed()
92-
{
93-
if (function_exists('mcrypt_create_iv')) {
94-
return;
95-
}
96-
97-
$this->secureRandom
98-
->expects($this->atLeastOnce())
99-
->method('nextBytes')
100-
;
101-
102-
$encoder = new BCryptPasswordEncoder($this->secureRandom, self::VALID_COST);
103-
$result = $encoder->encodePassword(self::PASSWORD, null);
104-
105-
$prefix = '$'.(version_compare(phpversion(), '5.3.7', '>=')
106-
? '2y' : '2a').'$';
107-
$salt = 'MDEyMzQ1Njc4OWFiY2RlZe';
108-
$expected = crypt(self::PASSWORD, $prefix.self::VALID_COST.'$'.$salt);
109-
110-
$this->assertEquals($expected, $result);
111-
}
11262
}

src/Symfony/Component/Security/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"php": ">=5.3.3",
2020
"symfony/event-dispatcher": "~2.1",
2121
"symfony/http-foundation": ">=2.1,<2.4-dev",
22-
"symfony/http-kernel": ">=2.1,<=2.3-dev"
22+
"symfony/http-kernel": ">=2.1,<=2.3-dev",
23+
"ircmaxell/password-compat": "1.0.*"
2324
},
2425
"require-dev": {
2526
"symfony/form": "~2.0",

0 commit comments

Comments
 (0)