Problems with Bcrypt password

Asked

Viewed 666 times

1

In my script I use the bcrypt function (through a class), and I have the following problem: At the time I perform the last queries before login I need to make the query where the encrypted email and password are equal to the bank’s email and password, but how do I do that? Since bcrypt uses a salt and generates random values every time a password is encrypted. Translating to MD5 would be more or less like what I wanted:

<?php 
 $email = "[email protected]";
 $senha = "123";
 $senhaCript= md5($senha);
 $select = (Select ... WHERE email = $email && senha = $senhaCript);

And so on, but the problem is that if I encrypt the password with Blowfish, it enters the if block of the invalid password.
**Detail: to register the user I use the same class

Login check page code:

<?php
session_start();

define('TENTATIVA_LOGIN', 5); 
define('TEMPO_BLOQUEIO', 30); 

require ("bcrypt.php");
require ("conexao.php");
$pdo = conectar();

if (isset($_SERVER['HTTP_REFERER']) && $_SERVER['HTTP_REFERER'] != "http://localhost:8080/Metta/html/login.php"):
    echo "<script>alert('A requisição não foi feita pelo formulário de login');</script>";
    echo "<script>window.history.back();</script>"; 
    exit();
endif;

$email = trim(strip_tags($_POST['email_form']));
$senha = trim(strip_tags($_POST['senha_form']));
$hashForm = Bcrypt::hash($senha);

$buscaSQL = $pdo->prepare('SELECT email FROM tbl_usuario WHERE email = ?');
$buscaSQL->execute(array($email));

if($buscaSQL->rowCount() <= 0):
    echo "<script>alert('O email digitado: ".$email. " não foi encontrado!');</script>";
    echo "<script>window.history.back();</script>"; 
    exit;
    endif;

$searchSQL = $pdo->prepare('SELECT email,senha FROM tbl_usuario WHERE email = ? && senha = ?');
$searchSQL->execute(array($email, $hashForm));
$linha = $searchSQL->fetch(PDO::FETCH_ASSOC);
$hashDB = $linha['senha'];

if(!(Bcrypt::check($hashForm, $hashDB))):
    echo "<script>alert('Senha inválida para este usuário!');</script>";
    echo "<script>window.history.back();</script>";
    exit;
else:
   echo "<script>alert('Login realizado com sucesso!');</script>";
   //echo "<script>window.location.href='index.php'();</script>";

$resultSQL = $pdo->prepare("SELECT * FROM tbl_usuario WHERE email = ? && senha = ?");
$resultSQL->execute(array($email , $senha));
$row = $resultSQL->fetch(PDO::FETCH_ASSOC);

$_SESSION['login'] = $email;
$_SESSION['cod_usuario'] = $row['cod_usuario'];
$_SESSION['nome'] = $row['nome'];
$_SESSION['tipo'] = $row['tipo'];
$_SESSION['permissoes'] = $row['permissoes'];
$_SESSION['img'] = $row['img'];
$_SESSION['link_box'] = $row['link_box'];
$_SESSION['fk_empresa'] = $row['fk_empresa'];
$_SESSION["logado"] = TRUE;
endif;
?>

Class code:

<?php
class Bcrypt {

/**
 * Default salt prefix
 * 
 * @see http://www.php.net/security/crypt_blowfish.php
 * 
 * @var string
 */
    protected static $_saltPrefix = '2a';

/**
 * Default hashing cost (4-31)
 * 
 * @var integer
 */
    protected static $_defaultCost = 10;

/**
 * Salt limit length
 * 
 * @var integer
 */
    protected static $_saltLength = 22;

/**
 * Hash a string
 * 
 * @param  string  $string The string
 * @param  integer $cost   The hashing cost
 * 
 * @see    http://www.php.net/manual/en/function.crypt.php
 * 
 * @return string
 */
    public static function hash($string, $cost = null) {
        if (empty($cost)) {
            $cost = self::$_defaultCost;
        }

        // Salt
        $salt = self::generateRandomSalt();

        // Hash string
        $hashString = self::__generateHashString((int)$cost, $salt);

        return crypt($string, $hashString);
    }

/**
 * Check a hashed string
 * 
 * @param  string $string The string
 * @param  string $hash   The hash
 * 
 * @return boolean
 */
    public static function check($string, $hash) {
        return (crypt($string, $hash) === $hash);
    }

/**
 * Generate a random base64 encoded salt
 * 
 * @return string
 */
    public static function generateRandomSalt() {
        // Salt seed
        $seed = uniqid(mt_rand(), true);

        // Generate salt
        $salt = base64_encode($seed);
        $salt = str_replace('+', '.', $salt);

        return substr($salt, 0, self::$_saltLength);
    }

/**
 * Build a hash string for crypt()
 * 
 * @param  integer $cost The hashing cost
 * @param  string $salt  The salt
 * 
 * @return string
 */
    private static function __generateHashString($cost, $salt) {
        return sprintf('$%s$%02d$%s$', self::$_saltPrefix, $cost, $salt);
    }

}
  • 1

    Saul, the passwords that are saved in the database have already been encrypted with Blowfish?

  • 1

    PHP already has the password_hash that uses Bcrypt (and in the future Argon2) because it does not use it?

  • 1

    I think the mistake is senha = ?, Bcrypt will use another salt, thus generating another "hash", not the same registered.

  • @Inkeliz you could give an example?

2 answers

2


When registering an entry (user) you encrypt... which will be stored a hash.

When performing the check you must re-encrypt the provided input which will generate a new value (other than the old one) what happens is that this class allows checking the hash... even if the result is different the hash is equal if the given values are equal.

example.php

// encriptar
$pwd =  '123456';
$mail = '[email protected]';

require 'Bcrypt.php';

$Bcrypt = new Bcript();
echo $Bcrypt->hash( $pwd.$mail, "04" );

// output: $2a$04$MTM3NzcyMDM3MTU4YmRiMOyxZ4rQqGaCJnk1k4OYaIfebUy4C1j/m

/** Ao realizar novamente a mesma operação com mesmos dados o resultado será diferente:
    echo $Bcrypt->hash( $pwd.$mail, "04");
    // output: $2a$04$NDc2Njc2NjUyNThiZGIxN.ujzdaxw57G.aznwtd/CHGoROfdp8GrO
*/

That is to say you saved in the bad database should not make a check gross you should use the class to check if the new hash generated with the same values is compatible with the hash old (stored in the database).

for example2.php

require 'Bcrypt.php';

$Bcrypt = new Bcript();

// hash armazenada no banco de dados
$dbPwd = "$2a$04$MTM3NzcyMDM3MTU4YmRiMOyxZ4rQqGaCJnk1k4OYaIfebUy4C1j/m"

// input do usuário
$pwd = "123456";
$mail = "[email protected]";

// verificar

if ( !$Bcrypt->check($pwd.$mail, $dbPwd) ) {
     // false
} else {
     // true
}

The logic in these examples must be observed. You must have some information to search in the database... usually the user’s email for if you are registered to return to hash stored and compare using class.

I think this is it

  • Thank you very much, it helped a lot!

  • I spent the afternoon doing this

2

In recent versions of PHP there is a password_hash and the password_verify which is safer than this class and both use Bcrypt.

To use the password_hash just do this:

$email = '[email protected]';
$senha = '123456';

$senha = password_hash($senha, PASSWORD_BCRYPT, [cost => 12]);

Saves the $senha and $email in the database. The salt should be generated each time and should not be constant, much less generated using mt_rand as is done in the class above, PHP itself says "This Function does not generate cryptographically Secure values, and should not be used for Cryptographic purposes.", is not my invention.


After that to do the login use the password_verify it is protected against attacks side-Channel like the timing Attacks, unlike the crypt($string, $hash) === $hash:

$email = '[email protected]';
$senha = '123456';

$pega_usuario = 'SELECT senha FROM tabela WHERE email = ?';
//...
$hash_do_usuario_do_banco = $pega_usuario['senha'];

if(password_verify($senha, $hash_do_usuario_do_banco)){
  // Senha válida
}else{
  // Senha inválida
}

If you want to use the class I recommend making some changes for greater security, these are the ones I detected:

  • Exchange the uniqid(mt_rand(), true) for random_bytes(), the mt_rand is not good for this purpose.
  • Remove the base64_encode is vulnerable to attacks in side-Channels, "content-Leaking", the str_replace must also be, best remove, the bin2hex() is still vulnerable.
  • Change the comparison crypt($string, $hash) === $hash for the same reason above and in its place use hash_equals().

This way would be the modifications, compatible with PHP 7:

class Bcrypt {

/**
 * Default salt prefix
 * 
 * @see http://www.php.net/security/crypt_blowfish.php
 * 
 * @var string
 */
    protected static $_saltPrefix = '2a';

/**
 * Default hashing cost (4-31)
 * 
 * @var integer
 */
    protected static $_defaultCost = 10;

/**
 * Salt limit length
 * 
 * @var integer
 */
    protected static $_saltLength = 22;

/**
 * Hash a string
 * 
 * @param  string  $string The string
 * @param  integer $cost   The hashing cost
 * 
 * @see    http://www.php.net/manual/en/function.crypt.php
 * 
 * @return string
 */
    public static function hash($string, $cost = null) {
        if (empty($cost)) {
            $cost = self::$_defaultCost;
        }

        // Salt
        $salt = self::generateRandomSalt();

        // Hash string
        $hashString = self::__generateHashString((int)$cost, $salt);

        return crypt($string, $hashString);
    }

/**
 * Check a hashed string
 * 
 * @param  string $string The string
 * @param  string $hash   The hash
 * 
 * @return boolean
 */
    public static function check($string, $hash) {
        return hash_equals(crypt($string, $hash), $hash);
    }

/**
 * Generate a random base64 encoded salt
 * 
 * @return string
 */
    public static function generateRandomSalt() {

        $seed = random_bytes(self::$_saltLength);

        return bin2hex($seed);
    }

/**
 * Build a hash string for crypt()
 * 
 * @param  integer $cost The hashing cost
 * @param  string $salt  The salt
 * 
 * @return string
 */
    private static function __generateHashString($cost, $salt) {
        return sprintf('$%s$%02d$%s$', self::$_saltPrefix, $cost, $salt);
    }

}

There may be other problems!

  • Thank you so much I will follow your advice and php and use the password Verify

Browser other questions tagged

You are not signed in. Login or sign up in order to post.