Welcome Guest, Not a member yet? Register   Sign In
password hashing doesn't work for all special characters
#11

Yes, sorry, I have a text file of a few fake passwords that I've been copy/pasting to test and just grabbed a different one this time.

The login method already has a check on the password matching, and the function used is basically just password_verify but also fails over to crypt on PHP < 5.5.
PHP Code:
public function check_passwd$hash$password )
    {
        if( 
is_php('5.5') && password_verify$password$hash ) ){
            return 
TRUE;
        }else if( 
$hash === crypt$password$hash ) ){
            return 
TRUE;
        }

        return 
FALSE;
    } 

So now I have two checks on the password validating in the login method:
PHP Code:
// Confirm user
if( ! $this->_user_confirmed$auth_data$requirement$passwd ) )
{
    
// Login failed ...
    
log_message(
        
'debug',
        
"\n user is banned             = " . ( $auth_data->banned === 'yes' 'no' ) .
        
"\n password in database       = " $auth_data->passwd .
        
"\n supplied password match    = " . ($this->check_passwd$auth_data->passwd$passwd ) ? "True" "False") .                                                         
                                    
"\n Password Validation        = " . (password_verify($auth_data->passwd$passwd) ? "Passed" "Failed") .
        
"\n required level or role     = " . ( is_array$requirement ) ? implode$requirement ) : $requirement ) . 
        
"\n auth level in database     = " $auth_data->auth_level 
        
"\n auth level equivalant role = " $this->roles[$auth_data->auth_level]
    );


Logs:
Code:
DEBUG - 2020-08-04 23:20:26 -->
password stored in DB       = $2y$11$8oe4JRwsm1LK1S8MqPd0sOQobASUdOIzx.KeFXO5bT.Tm.Ar5h86.
DEBUG - 2020-08-04 23:20:27 -->
Password Validation       = Passed

DEBUG - 2020-08-04 23:21:35 -->
string     = admin
password   = 1aA!@#%^&*()-_=+{};:,<.>

DEBUG - 2020-08-04 23:21:36 -->
user is banned             = no
password in database       = $2y$11$8oe4JRwsm1LK1S8MqPd0sOQobASUdOIzx.KeFXO5bT.Tm.Ar5h86.
supplied password match    = False
Password Validation        = Failed
required level or role     = 1
auth level in database     = 9
auth level equivalant role = admin
Reply
#12

Okay, try to log the password before "password stored in DB". That are the only place I can think about it being changed. As it looks correct on everything else.
Reply
#13

The first argument to password_verify is the password, and the second argument is the hash. It looks like you have these reversed.

https://www.php.net/manual/en/function.p...verify.php
Reply
#14

(This post was last modified: 08-05-2020, 09:23 AM by BilltheCat.)

(08-05-2020, 12:25 AM)ojmichael Wrote: The first argument to password_verify is the password, and the second argument is the hash. It looks like you have these reversed.

https://www.php.net/manual/en/function.p...verify.php

Good catch!  That was a copy/paste error on my part for sure, and I didn't try a "good" password that would have shown the typo.

So I ran a couple of new tests, one that works, and one that doesn't.  Here's the two log results with the different passwords:


Code:
DEBUG - 2020-08-05 15:18:16 -->
string    = admin
password  = 1aA!@#%^&*()-_=+{};:,<.>

DEBUG - 2020-08-05 15:18:16 -->
user is banned            = no
password in database      = $2y$11$64JQMc2Z3D680ePi/5iee.YvFed4FS1/Jt4CMn3xfXlZjIM1o43/6
supplied password match    = False
Password Validation        = Failed
required level or role    = 1
auth level in database    = 9
auth level equivalant role = admin



Code:
DEBUG - 2020-08-05 15:26:41 -->
string    = admin
password  = KA83**8!d#

DEBUG - 2020-08-05 15:26:41 -->
password in database      = $2y$11$Plsgi0m6m7Np8ZP6VFyAf.C2EqcAbs5ZLjBH9uJZnXtgcZu3yXXVS
supplied password match    = True
Password Validation        = Passed
required level or role    = 1
auth level in database    = 9
auth level equivalant role = admin

(08-04-2020, 11:12 PM)jreklund Wrote: Okay, try to log the password before "password stored in DB". That are the only place I can think about it being changed. As it looks correct on everything else.

I think we have a winner!


user_model log:
Code:
DEBUG - 2020-08-05 15:35:25 -->
password before DB      = 1aA!@#%^&amp;*()-_=+{};:,&lt;.&gt;
DEBUG - 2020-08-05 15:35:25 -->
password stored in DB      = $2y$11$s4QbphN38IDNphpnG/g4d.nvPsCMgjtKi.UEgPNvKerTDu3yH.kk2
DEBUG - 2020-08-05 15:35:25 -->
Password Validation      = Passed


authentication log: 
Code:
DEBUG - 2020-08-05 15:40:05 -->
string    = admin
password  = 1aA!@#%^&*()-_=+{};:,<.>

DEBUG - 2020-08-05 15:40:06 -->
user is banned            = no
password in database      = $2y$11$s4QbphN38IDNphpnG/g4d.nvPsCMgjtKi.UEgPNvKerTDu3yH.kk2
supplied password match    = False
Password Validation        = Failed
required level or role    = 1
auth level in database    = 9
auth level equivalant role = admin

Confirmed.... the issue is that my change_password method is using html_escape, but my login method was not.  Updated the login method, and it works as expected for all passwords.

Thanks for all your helpful suggestions!
Reply
#15

You need to base64_encode them first.

PHP Code:
$stored password_hash(
    base64_encode(
        hash('sha256'$_POST['password'], true)
    ),
    PASSWORD_DEFAULT
);
// ...
if (password_verify(
    base64_encode(
        hash('sha256'$_POST['password'], true)
    ),
    $stored
)) {
    // Success :D
} else {
    // Failure :(


SEEImplementing Secure User Authentication in PHP Applications with Long-Term Persistence (Login with "Remember Me" Cookies)
What did you Try? What did you Get? What did you Expect?

Joined CodeIgniter Community 2009.  ( Skype: insitfx )
Reply
#16

Thanks @InsiteFX, that's a great article and I should do some more reading on that... but I've got a solid user base already, and this issue has just been popping up occasionally.  How would I implement a base64_encode/decode process on passwords that already exist?
Reply
#17

(This post was last modified: 08-05-2020, 12:25 PM by jreklund.)

You should not use html_escape at all. As the user supplied password have been altered.

You should only escape on output not input.
Reply
#18

(This post was last modified: 08-05-2020, 02:04 PM by BilltheCat.)

(08-05-2020, 12:24 PM)jreklund Wrote: You should not use html_escape at all. As the user supplied password have been altered.

You should only escape on output not input.

Yeah, I've been thinking about that, and didn't like it.  It came that way from community_auth but I'm changing it as you suggest.
PHP Code:
$this->_change_password(
                    set_value('passwd'''FALSE), //wasn't FALSE by default
                    set_value('passwd_confirm'''FALSE), //wasn't FALSE by default
                    set_value('user_identification'),
                    set_value('recovery_code')
            ); 

It looks like I have an old version.... just checked out community_auth on bitbucket, and he's updated the section.

PHP Code:
$this->_change_password(
                
$this->input->post('passwd'),
                
$this->input->post('passwd_confirm'),
                
set_value('user_identification'),
                
set_value('recovery_code')
            ); 
Reply
#19

(This post was last modified: 08-13-2020, 10:44 AM by InsiteFX.)

Here are working examples of password_hash(), password_verify() and password_needs_rehash().

Controller: HashGenerator.php

PHP Code:
<?php namespace App\Controllers;


/**
 * Class HashGenerator Controller
 *
 * @package App\Controllers
 */
class HashGenerator extends BaseController
{
    
/**
     * Class properties go here.
     * -------------------------------------------------------------------
     * public, private, protected, static and const.
     */

    /**
     * @var  array - Holds the view file data
     */
    
public $data = [];

    
/**
     * @var string - Yoour password to hash.
     */
    
protected $password 'password';

    
/**
     * @var - The hashed password
     */
    
protected $hash;

    
/**
     * @var - The stored hash.
     */
    
protected $stored;

    
// -------------------------------------------------------------------

    /**
     * __construct ()
     * -------------------------------------------------------------------
     *
     * Class    Constructor
     *
     * Do not use this for Production without security in place.
     * 
     * NOTE: Not needed if not setting values or extending a Class.
     *
     */
    
public function __construct()
    {

    }

    
// -------------------------------------------------------------------

    /**
     * index ()
     * -------------------------------------------------------------------
     *
     * NOTE: The database password field should be VARCHAR(255).
     *
     */
    
public function index()
    {
        
$cost 12;

        
// Hash the password.
        
$this->hash   $this->passwordHash($cost$this->password);
        
$this->stored $this->hash;
        echo 
'Password Hashed = '.$this->hash.'<br>';

        
// Verify the password.
        
$this->hash $this->passwordVerify($cost$this->password$this->hash);
        echo 
'Password Verify = '.$this->hash.'<br>';

        
// Changed cost - password needs rehash. Generates a new password hash.
        
$cost 11;
        
$this->hash $this->passwordVerify($cost$this->password$this->stored);
        echo 
'Password Verify Rehash = '.$this->hash;
    }

    
// -------------------------------------------------------------------

    /**
     * passwordHash ()
     * -------------------------------------------------------------------
     *
     * @param  int    $cost
     * @param  string $password
     * @return false|string|null
     */
    
protected function passwordHash(int $coststring $password)
    {
        
// The cost parameter can change over time as hardware improves
        
$options = [
            
'cost' => $cost,
        ];

        return 
password_hash(
            
base64_encode(
                
hash('sha256'$passwordtrue)
            ), 
PASSWORD_DEFAULT$options
        
);
    }

    
// -------------------------------------------------------------------

    /**
     * passwordVerify ()
     * -------------------------------------------------------------------
     *
     * @param  int    $cost
     * @param  string $password
     * @param  string $hash
     * @return false|string|null
     */
    
protected function passwordVerify(int $coststring $passwordstring $hash)
    {
        
$newHash '';

        
// The cost parameter can change over time as hardware improves
        
$options = [
            
'cost' => $cost,
        ];

        
// Verify stored hash against plain-text password
        
if (password_verify(base64_encode(hash('sha256'$passwordtrue)),    $hash))
        {
            
// Check if a newer hashing algorithm is available or the cost has changed
            
if (password_needs_rehash($hashPASSWORD_DEFAULT$options))
            {
                
// If so, create a new hash, and replace the old one
                
$newHash password_hash(
                    
base64_encode(
                        
hash('sha256'$passwordtrue)
                    ), 
PASSWORD_DEFAULT$options
                
);
            }

            
// Update the users database record. password = $newHash

            // Then login the user

            // Used for the example, can remove later.
            
return $newHash;
        }
    }

    
// -------------------------------------------------------------------

}   // End of HashGenerator Controller Class.

/**
 * -----------------------------------------------------------------------
 * Filename: HashGenerator.php
 * Location: ./app/Controllers/HashGenerator.php
 * -----------------------------------------------------------------------
 */ 

I hope this helps other to understand how the password routines operate.

The Controller is a CI 4 Controller but you should be able to copy everything to a CI 3 Controller.
What did you Try? What did you Get? What did you Expect?

Joined CodeIgniter Community 2009.  ( Skype: insitfx )
Reply




Theme © iAndrew 2016 - Forum software by © MyBB