Bcrypt password problems

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 the same as the email and password of the bank, but how do I do this? Since bcrypt uses a salt and generates random values every time a password is encrypted. Translating to MD5 would be more or less 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);
    }

}
    
asked by anonymous 06.03.2017 / 19:45

2 answers

2

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

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

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
*/

In other words you save in the database but you should not do a raw check you should use the class to check if the new hash generated with the same values is compatible with the old hash (stored in the database).

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 should be observed. You should have some index to look for in the database ... usually the user's email in case you are registered return to hash stored and compare using the class.

I think this is

    
06.03.2017 / 20:11
2

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

To use password_hash just do this:

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

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

Saves $senha and $email to the database. The salt must be generated every time and should not be constant, much less generated using mt_rand as is done in the class above, the 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 make login use password_verify it is protected against side-channel attacks like timing attacks , in contrast of 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 :

  • Change uniqid(mt_rand(), true) to random_bytes() , mt_rand is not good for this purpose.
  • Remove% with% is vulnerable to attacks on side-channels , content-leaking, base64_encode should also be better removed , str_replace is still vulnerable .
  • Change the bin2hex() comparison for the same reason above and instead use crypt($string, $hash) === $hash .

In this way, the modifications would be 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!

    
06.03.2017 / 22:15