Desde que aprendi PHP, me disseram pra armazenar no banco de dados o hash das senhas dos usuários, particularmente aplicando o algoritmo MD5. Mas hoje fiquei com uma pulga atrás da orelha e resolvi pesquisar para saber se este é o método mais adequado para tal fim. E, adivinha só: não é. Topei com dois artigos muito bons que explicam um método muito mais seguro: utilizando a função crypt().
Primeiro, apresento como não armazenar senhas no banco de dados, e porquê.
Não salve as senhas como texto puro
Isto deveria ser óbvio. Se alguém conseguir acessar seu banco de dados, todos as contas de usuário estarão comprometidas. E não é só isso: as pessoas tendem a usar a mesma senha em sites diferentes, daí estas outras contas estariam comprometidas também. Seu site nem precisaria ser hackeado; um administrador do sistema de um servidor compartilhado poderia facilmente ver seu banco de dados.
Não invente um sistema de segurança de senhas
É muito provável que você não seja um expert em segurança. É melhor usar uma solução que funcione comprovadamente do que você inventar alguma coisa.
Não 'encripte' senhas
A encriptação pode parecer uma boa ideia, mas o processo é reversível. Qualquer um com acesso ao seu código não teria problema nenhum em transformar as senhas de volta. Segurança por obscuridade não é o suficiente.
Não utilize MD5
Armazenar os hashes das senhas é um passo na direção certa. Funções criptográficas como o MD5 são irreversíveis, o que torna mais difícil descobrir a senha original. Para validar um hash de uma senha, simplesmente aplique o hash na senha novamente quando o usuário fizer login e compare os hashes.
$senha = 'swordfish'; $hash = md5($senha); // Valor: 15b29ffdce66e10527a65bc6d71ad94d
Note que isto torna impossível recuperar a senha original do banco de dados. Se um usuário esquecê-la, simplesmente gere uma nova.
Então por que não o MD5? Porque é muito fácil fazer uma lista de milhões de hashes de senhas (uma rainbow table) e comparar os hashes para achar a senha original (o mesmo vale para outros métodos, como SHA-1).
O MD5 também está sujeito à força bruta (tentando todas as combinações com um script automatizado), particularmente devido à colisões. Isto significa que senhas diferentes podem ter o mesmo hash, tornando ainda mais fácil encontrar uma que funcione.
Não use um salt universal
Um salt ("sal", em tradução livre) é uma string, adicionada à senha, para que a maioria das rainbow tables (ou ataques por dicionário) não funcionem.
$senha = 'swordfish'; $salt = 'alguma coisa aleatória'; $hash = md5($salt . $senha); // Valor: 10ca832b34c90eccc658ead13e7485eb
Isto é melhor do que aplicar apenas o MD5, mas alguém com acesso ao seu código poderia descobrir o salt e gerar uma rainbow table.
Mãos à obra!
Para um sistema mais seguro de armazenamento de senhas, utilizaremos a função crypt() com um salt único. Muitas pessoas acham que um salt precisa ser único para cada usuário, o que é verdade para muitos casos. Mas com o crypt() podemos usar um salt que é único para uma determinada senha. O salt utilizado acabará estando dentro do hash da senha - então não precisamos armazená-lo separadamente.
Gerando um salt
Podemos usar a seguinte string como um salt
$salt = '$2a$15$abcdefghijklmnopqrstuv$'
O problema é que este não é único, nem aleatório. As letras utilizadas foram 22 caracteres dentro dos conjuntos a-z, A-Z e 0-9. Idealmente deveríamos utilizar uma função aleatória, que poderia utilizar um timestamp, ou até mesmo outro método maluco! Eis um salt mais ideal:
$salt = '$2a$15$Ku2hb./9aA71tPo/E015h.$'
Vamos ver como ele é composto - são três partes. A primeira, $2a$, é um identificador para informar ao PHP que estamos utilizando o algoritmo BlowFish. A segunda parte, 15, é o parâmetro de custo. É um número entre 4 e 31, que indica o número de iterações do algoritmo - basicamente, quanto maior o número, mais demorado é o ataque à força bruta, e mais demorada é a geração da senha.
Para gerar um salt aleatório, pode-se utilizar o código a seguir:
$salt = '$2a$10$'; $salt .= strtr(base64_encode(mcrypt_create_iv(16, MCRYPT_DEV_URANDOM)),'+','.');
Aplicando e verificando o hash
Aplicar o hash é tão simples quanto isto:
$hash = crypt($senha, $salt);
E pronto, temos o hash correspondente, pronto para ser armazenado no banco de dados.
Para verificar se uma senha é válida, utiliza-se o mesmo método, mas no lugar do salt fornecemos o hash armazenado no banco de dados, da seguinte maneira:
$hashArmazenado = '$2a$15$Ku2hb./9aA71tPo/E015h.LsNjXrZe8pyRwXOCpSnGb0nPZuxeZP2';
$senhaATestar = 'passwords1';
if( crypt($senhaATestar, $hashArmazenado) === $hashArmazenado ){
echo 'Você entrou!';
} else{
echo 'Senha inválida';
}
Aplicar o hash na senha utilizando seu próprio hash retorna o mesmo hash. Isto é, se a senha a ser testada estiver correta, aplicar a função crypt() fornecendo como salt o hash armazenado anteriormente retorna o próprio hash. Simples assim.
Considerações adicionais
Se você está levando a segurança do seu sistema a sério, adote outros mecanismos para prevenir ataques às contas de usuários, como:
- Aumentar exponencialmente o tempo de espera após logins incorretos
- Limitar a quantidade de logins incorretos
- Enviar um email ao usuário para desbloquear a conta
- Exiga senhas fortes (minúsculas, maiúsculas, números e símbolos, por exemplo)
- Não limite o tamanho das senhas
Referências
Storing passwords with PHP. In: <http://codular.com/storing-passwords-php>How to store passwords safely with PHP and MySQL. In: <http://alias.io/2010/01/store-passwords-safely-with-php-and-mysql/>
Nenhum comentário:
Postar um comentário