-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Improved Bcrypt password encoder #6023
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d6b73e2
2acf6b1
70ac90b
e4ecdf7
3a00532
adcd750
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ CHANGELOG | |
----- | ||
|
||
* Added PBKDF2 Password encoder | ||
* Added BCrypt password encoder | ||
|
||
2.1.0 | ||
----- | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* 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; | ||
|
||
use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder; | ||
use Symfony\Component\Security\Core\Util\SecureRandomInterface; | ||
|
||
/** | ||
* @author Elnur Abdurrakhimov <[email protected]> | ||
* @author Terje Bråten <[email protected]> | ||
*/ | ||
class BCryptPasswordEncoder extends BasePasswordEncoder | ||
{ | ||
/** | ||
* A secure random generator | ||
* @var SecureRandomInterface | ||
*/ | ||
private $secure_random; | ||
|
||
/** | ||
* @var string | ||
*/ | ||
private $cost; | ||
|
||
/** | ||
* @param int $cost | ||
* @throws \InvalidArgumentException | ||
*/ | ||
public function __construct($cost) | ||
{ | ||
//TODO: add SecureRandomInterface $secure_random as an argument | ||
// to the consructor. Service id: security.secure_random | ||
//$this->secure_random = $secure_random; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please do it... |
||
|
||
$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); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function encodePassword($raw, $salt = null) | ||
{ | ||
if ($this->secure_random) { | ||
$random = $this->secure_random->nextBytes(16); | ||
} else { | ||
$random = $this->get_random_bytes(16); | ||
} | ||
$salt = $this->encodeSalt($random) | ||
|
||
return crypt($raw, '$2y$' . $this->cost . '$' . $salt); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function isPasswordValid($encoded, $raw, $salt = null) | ||
{ | ||
return $this->comparePasswords($encoded, crypt($raw, $encoded)); | ||
} | ||
|
||
/** | ||
* The blowfish/bcrypt used by PHP crypt uses and expects a different | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you should add a short description before the long description |
||
* 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 | ||
*/ | ||
protected function encodeSalt($random) | ||
{ | ||
$len = strlen($random); | ||
if ($len<16) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing spaces around the comparison operator |
||
throw new \InvalidArguementException( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wrong class name (btw, this shoould be unit tested, which would have shown you the issue with a Fatal Error) |
||
'The bcrypt salt needs 16 random bytes'); | ||
} | ||
if ($len>16) { | ||
$random = substr($random, 0, 16); | ||
} | ||
|
||
$base64_raw = strtr(base64_encode($random), '+', '.'); | ||
$base64_128bit = substr($base64_raw, 0, 21); | ||
$lastchar = substr($base64_raw, 21, 1); | ||
$lastchar = strtr($lastchar, 'AQgw','.Oeu'); | ||
$base64_128bit .= $lastchar; | ||
|
||
return $base64_128bit; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use camelCased names for all variables |
||
} | ||
|
||
/** | ||
* Get random bytes | ||
* | ||
* @param integer $count Number if random bytes needed | ||
* @return string String of random bytes that is $count bytes long | ||
*/ | ||
protected function get_random_bytes($count) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be deleted. There is no reason to duplicate the code |
||
{ | ||
$random = ''; | ||
|
||
if(function_exists('openssl_random_pseudo_bytes') && | ||
(strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { | ||
// This also calls /dev/urandom and all others sorts | ||
// of methods to get randomness, but is too slow | ||
// on windows because of a bug there. | ||
$random = openssl_random_pseudo_bytes($count); | ||
} | ||
|
||
if (strlen($random)<$count) { | ||
// Try /dev/urandom | ||
if (@is_readable('/dev/urandom')) { | ||
$fh = @fopen('/dev/urandom', 'rb'); | ||
if ($fh) { | ||
stream_set_read_buffer($fh, 0); | ||
$random=fread($fh, $count); | ||
fclose($fh); | ||
} | ||
} | ||
|
||
// Last resort, fallback to mt_rand | ||
$len = strlen($random); | ||
if ($len<$count) { | ||
for ($i=$len;$i<$count;++$i) { | ||
$random .= chr(mt_rand(0,255)); | ||
} | ||
} | ||
} | ||
|
||
return $random; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* 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\BCryptPasswordEncoder; | ||
|
||
/** | ||
* @author Elnur Abdurrakhimov <[email protected]> | ||
*/ | ||
class BCryptPasswordEncoderTest extends \PHPUnit_Framework_TestCase | ||
{ | ||
const PASSWORD = 'password'; | ||
|
||
/** | ||
* @expectedException \InvalidArgumentException | ||
*/ | ||
public function testCostBelowRange() | ||
{ | ||
new BCryptPasswordEncoder(3); | ||
} | ||
|
||
/** | ||
* @expectedException \InvalidArgumentException | ||
*/ | ||
public function testCostAboveRange() | ||
{ | ||
new BCryptPasswordEncoder(32); | ||
} | ||
|
||
public function testCostInRange() | ||
{ | ||
for ($cost = 4; $cost <= 31; $cost++) { | ||
new BCryptPasswordEncoder($cost); | ||
} | ||
} | ||
|
||
public function testResultLength() | ||
{ | ||
$encoder = new BCryptPasswordEncoder(4); | ||
$result = $encoder->encodePassword(self::PASSWORD); | ||
$this->assertEquals(60, strlen($result)); | ||
} | ||
|
||
public function testValidation() | ||
{ | ||
$encoder = new BCryptPasswordEncoder(4); | ||
$result = $encoder->encodePassword(self::PASSWORD); | ||
$this->assertTrue($encoder->isPasswordValid($result, self::PASSWORD)); | ||
$this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword')); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a camelCased name