Stan
Stan

Reputation: 491

Does Symfony derive the salt from the hash or isn't the hash salted at all?

I'm implementing a password reset functionality using Symfony2. I have questions about hashing and salting the confirmation code used to validate the reset request.

The $user variable contains an instance of Acme\SecurityBundle\Model\User. The algorithm for this class is bcrypt and the cost is 15.

Acme\SecurityBundle\Model\User

namespace Acme\SecurityBundle\Model;

use Acme\SecurityBundle\Model\om\BaseUser;
use Symfony\Component\Security\Core\Util\SecureRandom;

class User extends BaseUser
{
    public function getSalt()
    {
        $random = new SecureRandom();
        return base64_encode($random->nextBytes(128 / 8));
    }
}



Hashing and validating happens inside a controller as follows.


Hashing:

// Generate confirmation code
$tokenGenerator = new UriSafeTokenGenerator();
$resetConfirmationCodePlain = substr($tokenGenerator->generateToken(), 0, 20);
// Hash confirmation code
$factory = $this->get('security.encoder_factory');
$encoder = $factory->getEncoder($user);
$resetConfirmationCode = $encoder->encodePassword($resetConfirmationCodePlain, $user->getSalt());


Validating:

// Validate confirmation code
$factory = $this->get('security.encoder_factory');
$encoder = $factory->getEncoder($user);
$isValid = $encoder->isPasswordValid(
    $user->getResetConfirmationCode(), $confirmationCode, null
);


As you can see a salt is added while hashing, but not added while validating. Nonetheless the confirmation code validated successfully.

Can someone explain this? Does Symfony derive the salt from the hash or isn't the hash salted at all?

P.S. I update a user's password in the same way.

Upvotes: 0

Views: 3059

Answers (2)

martinstoeckli
martinstoeckli

Reputation: 24141

Have a look at the hash-value itself...

$2y$10$nOUIs5kJ7naTuTFkBy1veuK0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa
 |  |  |                     |
 |  |  |                     hash-value = K0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa
 |  |  |
 |  |  salt = nOUIs5kJ7naTuTFkBy1veu (22 characters)
 |  |
 |  cost-factor = 10 = 2^10 iterations
 |
 hash-algorithm = 2y = BCrypt

...you can see that the salt is included in the stored hash-value. The verification function can read the used salt and other parameters from this stored hash, that's why you don't have to pass this parameters to the function.

According to Symfony's source code, the BCryptPasswordEncoder internally uses the PHP function password_hash(). This function will generate a safe salt if you omit the "salt" parameter, so i would recommend to leave out this parameter. If you pass a salt parameter, the function will take care that the salt has a valid format.

Upvotes: 2

frumious
frumious

Reputation: 1575

You're halfway there. The final hash produced using BCrypt comprises the cyphertext, the cost, and the salt used. The PHP password_hash function is general, and allows you to pass in a salt (this will be required for some algorithms), but if not provided on encode BCrypt will generate one, and on decode presumably it's ignored (since you can get it from the hash), so it's fine to pass in null.

So not only do you not need to pass in a salt when decoding, you probably shouldn't pass one in when encoding too, since BCrypt will take care of that in a cryptographically secure way.

See this question and the PHP docs for password_hash()

Upvotes: 0

Related Questions