Desenvolvimento - PHP
Criptografando senhas usando bcrypt (Blowfish) no PHP
Neste artigo vamos nos aprofundar mais sobre criptografia de dados com php, vamos falar sobre o bcrypt(Blowfish).
por Thiago BelemSuporte ao bcrypt
O PHP suporta hashing via bcrypt através da função crypt() que está presente desde o PHP 4, e serve pra trabalhar com hashings de mão única (como o MD5 e SHA1).
Usando o bcrypt
O bcrypt precisa – obrigatóriamente – receber dois “parâmetros” pra funcionar: o salt e o custo de processamento. O salt nada mais é do que a sua garantia de que, dado um salt aleatório, a mesma senha nunca será igualmente hasheada duas vezes… não importa que você criptografe a mesma senha 100 vezes, se o salt for diferente nas 100 vezes, o resultado final será sempre diferente. Para o bcrypt funcionar:
- O salt precisa ser uma string de 22 caracteres que respeite a expressão regular ./0-9A-Za-z.
- O custo deve ser um número inteiro entre 4 e 31, outro detalhe é que o custo precisa ter dois dígitos, então números menores que 10 precisam ter zero à esquerda
Fiz alguns testes e meu computador quase parou quado usei um custo de 15 — O custo é a potência de 2, então 2^15 equivale a 32.768 ciclos, já 2^31 equivaleria a 2.147.483.648 ciclos
O custo de processamento influencia diretamente nas tentativas de ataque de força bruta, quanto maior, mais lento, quanto mais lento, melhor.
Criptografando senhas usando bcrypt
Basicamente, pra criptografar a senha “olá mundo“, com o salt “Cf1f11ePArKlBJomM0F6aJ” à um custo de processamento de 8, você faria algo assim:
<?php $senha = 'ola mundo'; $custo = '08'; $salt = 'Cf1f11ePArKlBJomM0F6aJ'; // Gera um hash baseado em bcrypt $hash = crypt($senha, '$2a$' . $custo . '$' . $salt . '$');
O que fizemos foi passar dois valores para a função crypt(): o valor a ser criptografado (a senha em si), e uma string ‘$2a$08$Cf1f11ePArKlBJomM0F6aJ$‘, que é composta por três partes (separadas por cifrão):
- O método de hashing - 2a - que fará com que o bcrypt/blowfish seja usado
- O custo - 08
- O salt - Cf1f11ePArKlBJomM0F6aJ
Isso no vai gerar o seguinte hash:
$2a$08$Cf1f11ePArKlBJomM0F6a.EyvTNh6W2huyQi5UZst5qsHVyi3w5x.
Que é o valor que você deve salvar no banco de dados.
Caso essa mesma senha seja criptografada com o mesmo salt e o mesmo custo, o resultado será idêntico… caso você mude o salt (que deve ser gerado de forma aleatória) o resultado seria diferente.
Vale lembrar que o hash gerado terá sempre 60 caracteres, então você pode modelar a sua coluna que armazena a senha como CHAR(60) ou VARCHAR(60) se você estiver usando o MySQL.
Verificando e validando senhas usando bcrypt
Agora suponhamos que o seu usuário está fazendo o login, e o único valor que você tem é o nome de usuário (ou email, tanto faz) e a senha que ele digitou no formulário (ola mundo), como comparar isso com o valor que está no banco para verificar se os dados estão válidos?
Já que você está trabalhando com um salt gerado aleatoriamente, é impossível gerar um novo hash que seja igual ao hash que está no banco… correto? ERRADO!
Para comparar uma senha texto-plano com um já hasheado, é só usar esse próprio valor hasheado como hash da senha text-plano, vejam como é simples:
<?php // Senha digitada pelo usuário (veio do formulário) $senha = 'ola mundo'; // Senha já criptografada (salva no banco) $hash = '$2a$08$Cf1f11ePArKlBJomM0F6a.EyvTNh6W2huyQi5UZst5qsHVyi3w5x.'; if (crypt($senha, $hash) === $hash) { echo 'Senha OK!'; } else { echo 'Senha incorreta!'; }
É ou não é sensacional? Você pode gerar o mesmo hash, tendo a senha original e o hash resultado, sem precisar do salt original! Isso garante que você pode gerar um salt aleatório sempre que for criptografar a senha de alguém.
E é aí que o custo entra em jogo… mesmo durante um ataque de força bruta, o atacante pode tentar diferentes combinações de “senha original” mas o custo vai tornar a operação toda tão lenta que não vai valer o esforço.
Uma pequena classe para facilitar a sua vida
Criei uma pequena classe Bcrypt que ajuda a fazer esse trabalho todo através de dois métodos bem simples de usar..
Primeiro, o código completo da classe:
<?php /** * Bcrypt hashing class * * @author Thiago Belem <contato@thiagobelem.net> * @link https://gist.github.com/3438461 */ 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 = 8; /** * 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); } }
Agora como você pode usar os métodos:
<?php // Encriptando a senha $senha = 'ola mundo'; $hash = Bcrypt::hash($senha); // $hash = $2a$08$MTgxNjQxOTEzMTUwMzY2OOc15r9yENLiaQqel/8A82XLdj.OwIHQm // Salve $hash no banco de dados // Verificando a senha $senha = 'ola mundo'; $hash = '$2a$08$MTgxNjQxOTEzMTUwMzY2OOc15r9yENLiaQqel/8A82XLdj.OwIHQm'; // Valor retirado do banco if (Bcrypt::check($senha, $hash)) { echo 'Senha OK!'; } else { echo 'Senha incorreta!'; }
Artigo originalmente publicado por Thiago Belem: Criptografando senhas no PHP usando bcrypt (Blowfish)