Symfony2 - Change encoders for existing users - php

If you have an existing code base with users and their passwords, how can you change the password encoder and have users' passwords update?
In other words, let's say all user passwords are in MD5 and you want to transition to PBKDF2. The common strategy is to simply re-hash the password whenever the user logs in next.
However, I'm not sure how to do this in Symfony. Would it be done in the login controller? Or is there a way to do it in the EncoderInterface object?

Check out this blog... seems like this is what you're looking for...
How to change the way Symfony2 encodes passwords
You need to extend MessageDigestPasswordEncoder class, overwrite its methods and copy that class to the Security folder in your bundle (create one if not exist)
Check out the following example of how to extend MessageDigestPasswordEncoder
use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder as BaseMessageDigestPasswordEncoder;
class MessageDigestPasswordEncoder extends BaseMessageDigestPasswordEncoder
{
private $algorithm;
private $encodeHashAsBase64;
public function __construct($algorithm = 'sha512', $encodeHashAsBase64 = true, $iterations = 5000)
{
$this->algorithm = $algorithm;
$this->encodeHashAsBase64 = $encodeHashAsBase64;
$this->iterations = $iterations;
}
protected function mergePasswordAndSalt($password, $salt)
{
if (empty($salt)) {
return $password;
}
return $salt.$password; // or do whatever you need with the password and salt
}
public function encodePassword($raw, $salt)
{
// this is the original code from the extended class, change it as needed
if (!in_array($this->algorithm, hash_algos(), true)) {
throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm));
}
$salted = $this->mergePasswordAndSalt($raw, $salt);
$digest = hash($this->algorithm, $salted, true);
// "stretch" hash
for ($i = 1; $i < $this->iterations; $i++) {
$digest = hash($this->algorithm, $digest.$salted, true);
}
return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest);
}
}
After you have your class ready update your config.yml
# app/config/config.yml
# ...
parameters:
security.encoder.digest.class: Ens\TestBundle\Security\MessageDigestPasswordEncoder

Related

Why does calling encodePassword() (or hashPasswor()) with identical salts and passwords produces diffent hashes in Symfony?

In UserPassword encoder,
public function encodePassword(UserInterface $user, string $plainPassword)
{
$encoder = $this->encoderFactory->getEncoder($user);
return $encoder->encodePassword($plainPassword, $user->getSalt());
}
encoder gets the salt from user entity.
I set a static variable to the getSalt() in User entity:
public function getSalt()
{
return 'my-static-salt';
}
But when I encode:
$password = $encoder->encodePassword($user, "my-password");
$password2 = $encoder->encodePassword($user, "my-password");
$password and $password2 are different from each other as if the encodePassword() method uses a random salt.
What am I missing?
Note for Symfony > 5.4
From Symfony 6 these classes and methods are named more appropriately replacing Encode with Hash. And moved from the Security Core package to the Password Hasher package:
For example,
Symfony\Component\Security\Core\Encoder\EncoderFactory becomes
Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory, and so on.
But the substance of the answer remains the same.
The EncoderFactory is, by default, giving you an instance of the NativePasswordEncoder (unless you have the libsodium library installed, in which case it would give you a SodiumPasswordEncoder).
If you look at NativePasswordEncoder::encodePassword() you'll see this:
public function encodePassword($raw, $salt)
{
if (\strlen($raw) > self::MAX_PASSWORD_LENGTH) {
throw new BadCredentialsException('Invalid password.');
}
// Ignore $salt, the auto-generated one is always the best
$encoded = password_hash($raw, $this->algo, $this->options);
if (72 < \strlen($raw) && 0 === strpos($encoded, '$2')) {
// BCrypt encodes only the first 72 chars
throw new BadCredentialsException('Invalid password.');
}
return $encoded;
}
Notice this comment:
// Ignore $salt, the auto-generated one is always the best
If you do not pass a salt string to password_hash(), it will generate its own randomly generated salt each time you call it, and store the salt within the result of the operation (and the hashing algorithm used).
(Similarly, in SodiumPasswordEncoder you'll see that $salt is not used at all, although a similar comment does not exist).
Further reading:
New in Symfony 4.3: Native Password Encoder
password_hash() docs
https://paragonie.com/book/pecl-libsodium/read/07-password-hashing.md

Created Hashed Password in PHP Class

So I'm trying to create a Blowfish encrypted password with a salt using a User class that I have created, which in turns extends an overall database object that uses Late Static Bindings to CRUD from my database. Anyway, I'm trying to get this darn thing to encrypt the password before I call the create() method and inset it onto my database but each time when I do put the information in the form it goes to a blank 'update.php' screen (update.php has all my isset($_POST[]) calls for all my forms) and nothing gets uploaded to my database. Here's the code so far...
Code in update.php
if (isset($_POST["createAdmin"])) {
$user = new Users();
$user->password = $user->password_encrypt($_POST['new_password']);
$user->username = $_POST['new_username'];
$user->first_name = $_POST['first_name'];
$user->last_name = $_POST['last_name'];
if($user->create()) {
$_SESSION['new_admin_message'] = $user->password;
redirect_to("../public/admin/manage_admin.php");
}
else {
$_SESSION['new_admin_message'] ="Admin didn't create successfully";
redirect_to("../public/admin/manage_admin.php");
}
}
Code in user.php (the user class)
<?php
require_once(LIB_PATH.DS.'database.php');
class Users extends DatabaseObject {
protected static $table_name="users";
protected static $db_fields = array('id', 'username', 'password', 'first_name', 'last_name');
public $id;
public $username;
public $password;
public $first_name;
public $last_name;
public static function password_encrypt($password) {
$hashed_format = "2y$10$"; // Tels PHP to use Blowfish with a "cost" of 10
$salt_length = 22; // Blowfish salts should be 22-characters or more
$salt = generate_salt($salt_length);
$format_and_salt = $hash_format . $salt;
$hash = crypt($password, $format_and_salt);
return $hash;
}
private function generate_salt($length) {
// Not 100% unique, not 100% random, but good enoguh for a salt
// MD5 returns 32 characters
$unique_random_string = md5(uniqid(mt_rand(), true));
// Valid caracters for a solt are [a-zA-Z0-9./]
$base64_string = base64_encode($unique_random_string);
// But not '+' which is valid in base64 encoding
$modified_base64_string = str_replace('+', ".", $base64_string);
//Truncate string to the correct length
$salt = substr($modified_base64_string, 0, $length);
return $salt;
}
There's a couple other methods in the class that aren't important for this particular problem. I'm relatively new to OOP and PHP in general so any help would be greatly appreciated. If you could leave a short description on how you fixed the problem that would be awesome too. Thanks!!
There are three things wrong with your code:
You cannot refer to a normal method from a static method. In order for your code to work you also have to make the generate_salt method static.
You use the wrong format variable ($hash_format should be $hashed_format) when concatenating the format and salt.
Your format is wrong. Look at the documentation. The blowfish format is:
$[algo]$[difficulty]$[salt]$
Your format comes out to be:
[algo]$[difficulty]$[salt]
So, change your method to something like this:
public static function password_encrypt($password) {
$format = '$2y$10$'.$this->generate_salt(22).'$';
return crypt($password, $format);
}
Another thing, which is not technically "wrong" but is not a good thing, is your salt method. You should generate your salt from a cryptographically stronger source, such as using the mcrypt extension or, if you are on *nix, even grabbing it from /urandom or /random. Creating a "random" string by calling a mish-mash of functions, and ending up with something that looks random enough, is not a good idea.
The best thing you could do is to use the password library that comes with PHP. It will handle all the password hashing for you, and will protect you from yourself. If you have PHP <5.5.0 then you should use the compatibility library.
In other words, you should change your code to this:
public static function password_encrypt($password) {
return password_hash($password, PASSWORD_BCRYPT, ['cost' => 10]);
}

How to use php 5.5 password api with Zend_Auth_Adapter_DbTable

Until now I have stored all passwords in plain text because the site is not live and I decided to wait for the new password api.
I have this code working for passwords in plain text:
<?php
$dbAdapter = Zend_Db_Table::getDefaultAdapter();
$authAdapter = new Zend_Auth_Adapter_DbTable($dbAdapter);
$authAdapter->setTableName('account')
->setIdentityColumn('account_id')
->setCredentialColumn('account_password');
// Get our authentication adapter and check credentials
$adapter = $authAdapter;
$adapter->setIdentity($values['account_id']);
$adapter->setCredential($values['password']);
$auth = Zend_Auth::getInstance();
$result = $auth->authenticate($adapter);
if ($result->isValid()) {
$user = $adapter->getResultRowObject();
$auth->getStorage()->write($user);
return true;
}
return false;
According to docs I should implement my own adapter and probably just change to make use of password_verify().
I'm missing the big picture here to how everything is working together.
My question is:
Witch object should I modify? $authAdaper or $auth
Any high level (or low level :D) example code would be appreciated.
All best
Adam
If you are looking to modify the way in which your authentication operates by adding the password_hash encryption then
you will need to do so within PHP.
As you still wish to use database authentication I think recreating this as a new adapter would be overkill. You could however, extend the current database adapter, such as:
class My_Auth_Adapter_DbTable extends Zend_Auth_Adapter_DbTable
{
public function setCredential($credential)
{
$this->_credential = password_hash($credential);
return $this;
}
}
This means that any password provided to the adapter will always be encrypted with the password_hash function.
This could however be acomplished outside the adapter by hashing the password prior to the call to setCredential.
$options = array('salt' => $config->passwordSalt);
$hashPassword = password_hash($plainTextPassword, PASSWORD_BCRYPT, $options);
$adpater->setCredential($hashPassword);
This method will allow you to modify the optional parameters before passing to the adapter.
Lastly, it is worth mentioning that the setCredentialTreatment method is normally used to provided password encryption, which is performed within the SQL statement (meaning you will need to use the MySQL commands and not password_hash).
$authAdapter->setTableName('user')
->setIdentityColumn('email')
->setCredentialColumn('password')
->setCredentialTreatment(sprintf("MD5(CONCAT(?,'%s'))", $config->passwordSalt));
Hashes created by password_hash() needs to be compared via ̀password_verify()` because 2 hashes for the same password are not always equals (at least not with BCRYPT or ARGON2).
<?php
$pass = 'foo';
var_dump(password_hash($pass, PASSWORD_BCRYPT) === password_hash($pass, PASSWORD_BCRYPT));
// bool(false)
var_dump(password_verify($pass, password_hash($pass, PASSWORD_BCRYPT)));
// bool(true)
Someone (s7anley) did a Zend_Auth_Adapter_DbTable extension that uses password_verify(), here it is (for reference):
<?php
class Base_Auth_Adapter_BcryptDbTable extends Zend_Auth_Adapter_DbTable
{
/**
* #inheritdoc
*/
protected function _authenticateCreateSelect()
{
$dbSelect = clone $this->getDbSelect();
$dbSelect->from($this->_tableName)
->where($this->_zendDb->quoteIdentifier($this->_identityColumn, true) . ' = ?', $this->_identity);
return $dbSelect;
}
/**
* #inheritdoc
*/
protected function _authenticateValidateResult($resultIdentity)
{
$passwordCheck = password_verify($this->_credential, $resultIdentity[$this->_credentialColumn]);
if (!$passwordCheck) {
$this->_authenticateResultInfo['code'] = Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID;
$this->_authenticateResultInfo['messages'][] = 'Supplied credential is invalid.';
return $this->_authenticateCreateAuthResult();
}
$this->_resultRow = $resultIdentity;
$this->_authenticateResultInfo['code'] = Zend_Auth_Result::SUCCESS;
$this->_authenticateResultInfo['messages'][] = 'Authentication successful.';
return $this->_authenticateCreateAuthResult();
}
}
The class name says "Bcrypt" but it will work just fine with any algorithm supported by password_hash().
You can use it like that:
$authAdapter = new Base_Auth_Adapter_BcryptDbTable($databaseAdapter, 'users', 'login', 'password');
$authAdapter
->setIdentity('my_username')
->setCredential('my_password') // "clear" password
// ->setCredentialTreatment(null) // Can't set any treatment on password (would be ignored)
;
// For any additional filtering of returned rows, use getDbSelect()
$authAdapter->getDbSelect()->where('active = "TRUE"');

PHP storing password with blowfish & salt & pepper

I want to store secure user passwords in a MySQL database with PHP.
How can I make it better?
My Class:
private static $algo = '$2a';
private static $cost = '$10';
private static $pepper = 'eMI8MHpEByw/M4c9o7sN3d';
public static function generateSalt($length) {
$randomBinaryString = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
$randomEncodedString = str_replace('+', '.', base64_encode($randomBinaryString));
return substr($randomEncodedString, 0, $length);
}
public static function generateHash($password) {
if (!defined('CRYPT_BLOWFISH'))
die('The CRYPT_BLOWFISH algorithm is required (PHP 5.3).');
$password = hash_hmac('sha256', $password, self::$pepper, false);
return crypt($password, self::$algo . self::$cost . '$' . self::generateSalt(22));
}
public static function checkPassword($hash, $password) {
$salt = substr($hash, 0, 29);
$password = hash_hmac('sha256', $password, self::$pepper, false);
$new_hash = crypt($password, $salt);
return ($hash == $new_hash);
}
Either use this answer's suggestions (for PHP >= 5.5), or the following class. Thanks to martinstoeckli for pointing out the password_hash functions. I read the code over, and the only different thing in password_hash that I can see is error-checking and DEV_URANDOM usage from the OS to generate a more random salt.
class PassHash {
public static function rand_str($length) {
$chars = "0123456789./qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM";
//only allowed chars in the blowfish salt.
$size = strlen($chars);
$str = "";
for ($i = 0; $i < $length; $i++)
$str .= $chars[rand(0, $size - 1)]; // hello zend and C.
return $str;
}
public static function hash($input) {
return crypt($input, "$2y$13$" . self::rand_str(22));
// 2y is an exploit fix, and an improvement over 2a. Only available in 5.4.0+
}
public static function hash_weak($input) {
return crypt($input, "$2a$13$" . self::rand_str(22)); }
// legacy support, Add exception handling and fall back to <= 5.3.0
public static function compare($input, $hash) {
return (crypt($input, $hash) === $hash);
}
}
It's what I've always used. A suggestion is also PHPass. It's tried and tested.
The only downfall in this script is that I generate random numbers from rand(), and not the source from the OS, but that's easily changed.
Also, there is no real reason to be using SHA256 hashing on top of bcrypt. SHA256 is weak, and can be broken with relatively little effort3.
In addition, hashing passwords is essential practice, but for true security, run all input through at least John the Ripper's wordlist1 to remove the most common passwords and inform a user to use a different password. Wordlists are used far more effectively than any bruteforce due to terribly weak passwords.
And as a final note, do not force your users to use symbols, uppercase and numbers, force them to use a long password2.
Length is everything (no humour intended) when it comes to bruteforcing passwords. Pretty much any preset cracker will be set to not go over 12 characters unless a config is edited. If you ever see a site with a "maximum length" on passwords, make sure to never re-use a password there, because they have no security whatsoever4.
1. Arbitrary choice of cracker; pick what you find to work best
2. http://xkcd.com/936/
3. Comparatively (it's several orders of magnitude faster and is technically security through obscurity)
4. I have even seen banks do this. Having a maximum length on their passwords made me switch banks.

Zend_Auth_Adapter_DbTable and PHP crypt

I am hashing my passwords in a Zend php application using PHP crypt(). However, I can't think of a solution for using this hash with Zend_Auth_Adapter_DbTable. Assuming I have a password hash stored after being run with crypt()...
//Salt and hash...
$salt = '$2a$07$'.$this->getSalt();
$data['password'] = crypt($user_object->password, $salt);
$this->_db_table->insert($data);
//Authentication...
$dbAdapter = Zend_Db_Table::getDefaultAdapter();
$authAdapter = new Zend_Auth_Adapter_DbTable($dbAdapter);
$authAdapter->setTableName('users')
->setIdentityColumn('username')
->setCredentialColumn('password')
//Now what? Possibly...
->setCredentialTreatment(/* But how? */);
How can I use the Zend_Auth_Adapter_DbTable table object with this kind of salting and hashing strategy? I've looked around, but can't really find any solutions outside of MD5 and SHA type hashing...
If you are storing the Salt in the user table, you should create your own adapter
If you have the salt somewhere else you just need to encrypt the password and then just pass it to the adapter with
$authAdapter->setCredential($cryptedPassword);
I have the same issue a couple of weeks ago, i ended up creating my own adapter, extending Zend_Auth_Adapter_DbTable
I actually backported the ZF2 Bcrypt lib but you should be able to use it with crypt method.
Take a look if you want AuthAdapter-DbTableBcrypt
So I wrote my own adapter to overcome this. Just include the file, pass it to a Zend_Auth adapter authenticate function with the details (Here I am using a login with email and a password):
class User_Authenticate_Adapter implements Zend_Auth_Adapter_Interface {
protected $_username;
protected $_password;
public function __construct($email, $password) {
$this->_email = $email;
$this->_password = $password;
}
public function authenticate() {
$dbTable = new Application_Model_DbTable_User();
$select = $dbTable->select()->where('email = ?', $this->_email);
$row = $dbTable->fetchRow($select);
if($row == null) {
return new Zend_Auth_Result(Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND,$this->_email);
}
$data = $row->toArray();
if(!crypt($data['password'], $this->_password)) {
return new Zend_Auth_Result(Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID,$this->_email);
}
else {
return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS);
}
}
}
Hope that helps somebody.
get the password before and use it as a salt in crypt function
$dbUser = new Application_Model_DbTable_User;
$data = $dbUser->fetchRow(array("username = ?" => $_POST["username"]));
$cryptedPassword = $data->password; // here is the salt
$authAdapter->setIdentity($_POST["username"])
->setCredential(crypt($_POST["password"], $cryptedPassword));

Categories