summaryrefslogtreecommitdiffstats
path: root/Core/Encoder
diff options
context:
space:
mode:
Diffstat (limited to 'Core/Encoder')
-rw-r--r--Core/Encoder/BCryptPasswordEncoder.php148
-rw-r--r--Core/Encoder/BasePasswordEncoder.php15
-rw-r--r--Core/Encoder/EncoderFactory.php2
-rw-r--r--Core/Encoder/Pbkdf2PasswordEncoder.php97
4 files changed, 252 insertions, 10 deletions
diff --git a/Core/Encoder/BCryptPasswordEncoder.php b/Core/Encoder/BCryptPasswordEncoder.php
new file mode 100644
index 0000000..1b7572d
--- /dev/null
+++ b/Core/Encoder/BCryptPasswordEncoder.php
@@ -0,0 +1,148 @@
+<?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;
+
+use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;
+use Symfony\Component\Security\Core\Util\SecureRandomInterface;
+
+/**
+ * @author Elnur Abdurrakhimov <elnur@elnur.pro>
+ * @author Terje BrĂ¥ten <terje@braten.be>
+ */
+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
+ *
+ * @throws \InvalidArgumentException if cost is out of range
+ */
+ public function __construct(SecureRandomInterface $secureRandom, $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').'$';
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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;
+ }
+}
diff --git a/Core/Encoder/BasePasswordEncoder.php b/Core/Encoder/BasePasswordEncoder.php
index ae1c7d4..c26c9ce 100644
--- a/Core/Encoder/BasePasswordEncoder.php
+++ b/Core/Encoder/BasePasswordEncoder.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\Security\Core\Encoder;
+use Symfony\Component\Security\Core\Util\StringUtils;
+
/**
* BasePasswordEncoder is the base class for all password encoders.
*
@@ -50,6 +52,8 @@ abstract class BasePasswordEncoder implements PasswordEncoderInterface
* @param string $salt the salt to be used
*
* @return string a merged password and salt
+ *
+ * @throws \InvalidArgumentException
*/
protected function mergePasswordAndSalt($password, $salt)
{
@@ -77,15 +81,6 @@ abstract class BasePasswordEncoder implements PasswordEncoderInterface
*/
protected function comparePasswords($password1, $password2)
{
- if (strlen($password1) !== strlen($password2)) {
- return false;
- }
-
- $result = 0;
- for ($i = 0; $i < strlen($password1); $i++) {
- $result |= ord($password1[$i]) ^ ord($password2[$i]);
- }
-
- return 0 === $result;
+ return StringUtils::equals($password1, $password2);
}
}
diff --git a/Core/Encoder/EncoderFactory.php b/Core/Encoder/EncoderFactory.php
index 9429441..8bad61f 100644
--- a/Core/Encoder/EncoderFactory.php
+++ b/Core/Encoder/EncoderFactory.php
@@ -51,6 +51,8 @@ class EncoderFactory implements EncoderFactoryInterface
* @param array $config
*
* @return PasswordEncoderInterface
+ *
+ * @throws \InvalidArgumentException
*/
private function createEncoder(array $config)
{
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);
+ }
+}