Tenho alguns artigos (beeem antigos) aqui no blog onde falo sobre encriptação/hashing de senhas usando sha1, md5 e etc.
Esses métodos de hashing atendem - de forma satisfatória - quem está começando a trabalhar com programação e desenvolvimento, mas são notavelmente inseguros se comparados à outros métodos (como o bcrypt).
Recomendo a leitura de um artigo meu (não tão antigo assim), onde falo sobre a bcrypt se baseia.
Desde então, eu tenho procurado me aprofundar um pouco mais no assunto, e recentemente comecei a usar o bcrypt nos meus projetos…
Suporte ao bcrypt
O PHP suporta hashing via 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:
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:
É 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:
Agora como você pode usar os métodos:
Espero que tenham gostado! :)