No artigo Autenticação e criptografia de senhas, vimos os mecanismos que a linguagem PHP oferece para gerar hash, especialmente para armazenamento seguro de senhas, além do processo de conferência de um hash com a senha real. Porém, até então não existia uma padronização sobre este processo. Com isso em vista, a equipe de desenvolvimento do PHP implementou uma API para geração de hash de senhas.
Este artigo irá apresentar esta, que é uma das novidades do PHP 5.5.
Características da API de hash de senhas
Basicamente, a API servirá como um wrapper que irá acessar os recursos já conhecidos da função crypt. Conforme mostrado no artigo sobre autenticação e criptografia de senhas, a função crypt aceita diferentes algoritmos de criptografia, mas, para usá-los, é necessário montar o sal manualmente. Cada sal possui um formato próprio e precisa de um pouco de código para gerá-los. Um dos objetivos da API de geração de hash de senhas foi tornar este processo mais simples.
Inicialmente, a equipe de desenvolvimento colocou a disposição apenas a opção para utilização do algoritmo blowfish, que é o mecanismo mais robusto suportado pelo PHP até o momento. Porém, há planos de implementação de algoritmos ainda melhores, como o scrypt.
Funcionamento da API de hash de senhas
A API possui quatro funções:
- password_hash - responsável por gerar o hash de uma senha.
- password_verify - responsável por checar se uma senha é compatível com um hash.
- password_needs_rehash - responsável por checar se um hash informado utiliza determinado algoritmo com as opções específicas.
- password_get_info - responsável por devolver informações sobre um hash.
password_hash
Esta função é usada quando o usuário se registra no sistema (informando pela primeira vez a senha desejada) ou quando ele deseja alterar a sua senha atual. Após gerar o hash, ele deve ser armazenado (por exemplo, em um banco de dados).
A função recebe três parâmetros: (i) a senha a ser codificada, (ii) o algoritmo de criptografia (uma das constantes PASSWORD_...), e (iii) um array com opções específicas do algoritmo. Apenas o último parâmetro é opcional. Ela retorna uma string, que é o hash da senha.
No momento, o algoritmo suportado é o Blowfish, provido pela constante PASSWORD_BCRYPT. Ele aceita opção para especificar o sal e um custo. Podemos gerar um sal aleatório com a função mcrypt_create_iv, enquanto o custo precisa ser um valor entre 4 e 31 (o qual precisa ser testado no hardware que irá realizar o processamento da criptografia). Veja o exemplo de geração do hash com sal aleatório e custo "9":
$senha = 'minhasenhasecreta'; $algo = PASSWORD_BCRYPT; $opcoes = array( 'cost' => 9, 'salt' => mcrypt_create_iv(22, MCRYPT_DEV_URANDOM) ); $hash = password_hash($senha, $algo, $opcoes);
O hash gerado possui embutido o algoritmo, as opções e a senha codificada.
Observações: o sal necessário pelo algoritmo Blowfish precisa ter tamanho 22. O custo (cost) do algoritmo pode ser ajustado de acordo com o hardware e sua carga. Quanto mais alto, mais difícil quebrar o hash, porém, exige maior consumo de recursos de processamento. O ideal é um equilibrio entre estas duas pontas.
password_verify
Esta função é usada para verificar se uma senha informada é compatível com um hash armazenado. Normalmente ocorre durante o log-in do usuário, mas também pode ser usada em áreas do sistema que exigem autenticação no momento.
A função recebe dois parâmetros obrigatórios: a senha a ser conferida, e o hash que estava armazenado. Ela retorna true se a senha é compatível com o hash ou false se a senha não é compatível ou se ocorreu algum problema.
Veja um exemplo de como checar se a senha é válida (compatível com um hash armazenado):
// Consultar o hash do usuario no BD $hash = obter_hash_usuario($_POST['usuario']); // Obter senha informada no formulario de log-in $senha = $_POST['senha']; // Conferir se a senha eh valida $autenticado = password_verify($senha, $hash);
password_needs_rehash
Conforme comentado anteriormente, o "custo" do algoritmo de criptografia depende do hardware que irá processá-lo. Como o hardware evolui no decorrer do tempo, pode ser que o custo escolhido para gerar um hash acabe se tornando ultrapassado. Neste caso, é recomendada a atualização do hash com um custo mais elevado e adequado ao novo hardware. Isso pode ser feito quando o usuário informa a senha real (por exemplo, durante a autenticação no sistema ou durante a troca da senha atual).
Não é possível refazer o hash de uma senha apenas com o hash antigo. Por isso é necessário realizar esta operação quando se tem a senha original em mãos. Para evitar que usuários fiquem com hashes ultrapassados armazenados, é possível programar seu sistema para que, de tempos em tempos, percorra os hashes em busca de hashes ultrapassados e envie um e-mail para os respectivos usuários, solicitando a atualização da senha.
Para usar esta função, basta informar (i) o hash a ser testado, (ii) o algoritmo de criptografia, e (iii) as opções desejadas. A função vai retornar true se entender que o hash precisa ser refeito, e retornar false, caso contrário. Por exemplo, se você muda o custo do algoritmo, a função vai retornar true, mas se você muda apenas o sal, ela vai retornar false, pois o sal deveria sal algo randômico e a sua alteração não implica na necessidade de troca do hash.
Exemplo de uso:
...
if ($autenticado) {
$precisa_novo_hash = password_needs_rehash($hash, $algo, $opcoes);
if ($precisa_novo_hash) {
$novo_hash = password_hash($senha, $algo, $opcoes);
salvar_hash_usuario($usuario, $novo_hash);
}
}
password_get_info
Esta função recebe um hash e devolve um array com informações sobre o hash, como o ID do algoritmo usado, o nome do algoritmo usado e as opções usadas. Veja o exemplo:
$info = password_get_info($hash); var_dump($info); /* Imprime: array(3) { ["algo"]=> int(1) ["algoName"]=> string(6) "bcrypt" ["options"]=> array(1) { ["cost"]=> int(9) } } */
Se o hash informado for inválido, ela retorna o ID de algoritmo "0" (zero).
Hash e a Codificação de Caracteres
Um cuidado que se deve ter em relação à geração do hash de uma senha é sobre a codificação de caracteres usada. Como vimos no artigo sobre Unicode, o símbolo "ç", por exemplo, tem sua representação em bytes de forma diferenciada em ISO-8859-1 e em UTF-8. Os mecanismos de hash levam em consideração os bytes da string e, portanto, se um sistema migrou de uma codificação para outra, pode ter problema com a autenticação dos usuários que usaram estes tipos de símbolo em suas senhas.
Para evitar este problema, é recomendado que a senha passada para password_hash e password_verify tenham sempre a mesma codificação. Então, se um sistema migrou de ISO-8859-1 para UTF-8 e os hashes foram gerados a partir da codificação ISO-8859-1, então será necessário aplicar a função utf8_decode sobre a senha em UTF-8, antes de passar para as funções da API de hash.
3 comentários
Rubens, vc recomenda a instalação do Php 5.5 já?
Olá, Mauro
O PHP 5.5 ainda está em versão Alpha, portanto, recomendo a instalação dele apenas para testar as novas funcionalidades e para checar se algum sistema (em modo de desenvolvimento) continua estável.
Para sistemas em modo de produção, ele ainda não está pronto e não é recomendada sua utilização.
Abraço
Valeu Rubens, abraço...
Postar um comentário
Nota: fique a vontade para expressar o que achou deste artigo ou do blog.
Dica: para acompanhar as respostas, acesse com uma conta do Google e marque a opção "Notifique-me".
Atenção: o blogger não permite inclusão de tags nos comentários, por isso, use algum site externo para postar seu código com dúvidas e deixe o link aqui. Exemplo: pastebin.com