API para hash de senhas no PHP 5.5

Artigo que apresenta a API para hash de senhas introduzido no PHP 5.5.

Introdução
cadeado

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 Takiguti Ribeiro (autor do blog) disse...

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