Menu

PHP password_hash() e password_verify(): guardar senhas

Gere o hash de uma senha com password_hash($password, PASSWORD_DEFAULT), guarde o resultado de 60 caracteres e verifique um login com password_verify($password, $hash). Veja por que o hash muda a cada vez, o custo, password_needs_rehash, Argon2id e por que md5 e sha1 são errados para senhas.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Guarde senhas com password_hash($password, PASSWORD_DEFAULT) e verifique um login com password_verify($password, $hash), que retorna true ou false. Nunca guarde a senha em si, e nunca use md5() ou sha1() para isso.

Rode duas vezes: o hash é diferente a cada vez, e as duas versões são verificadas. O mesmo vale para todos os hashes desta página, então a sua saída não vai bater com a de mais ninguém.

Fazer login com password_verify

Um login compara a senha enviada com o hash salvo quando a conta foi criada. Este bloco "cadastra" um usuário e depois mostra um formulário de login; teste a senha certa (hunter2) e depois uma errada.

A mensagem de erro é a mesma para um e-mail desconhecido e uma senha errada. Dizer qual dos dois está errado conta a um atacante quais e-mails têm conta. Depois de um login bem-sucedido, um site real guarda o id do usuário na sessão, como mostra a página sobre sessões.

O que há dentro do hash

Um hash bcrypt é uma string que guarda tudo de que o password_verify() precisa: o algoritmo, o custo e o salt, seguidos do hash em si. O password_get_info() lê as primeiras partes de volta.

Como o salt fica guardado no hash, você precisa de uma única coluna no banco de dados, e dois usuários com a mesma senha ainda recebem hashes diferentes. Faça essa coluna VARCHAR(255): o bcrypt tem 60 caracteres, mas o PASSWORD_DEFAULT pode mudar para um algoritmo com saída mais longa.

Escolher o custo

O custo é o quanto cada hash é lento de propósito, e cada passo acima dobra o trabalho. O PHP 8.3 usa custo 10 para bcrypt (o PHP 8.4 aumentou o padrão para 12). Um login deve levar uns 100 a 300 ms de hashing; meça no seu próprio servidor:

Os números dependem da máquina, mas cada linha deve ser umas duas vezes a anterior. Mais alto é mais seguro contra quebra e mais lento para cada login, então escolha o custo mais alto que a sua página de login aguenta.

Atualizar hashes antigos com password_needs_rehash

Quando você aumenta o custo ou o PHP muda o PASSWORD_DEFAULT, os hashes existentes continuam funcionando, e o password_needs_rehash() diz quais estão desatualizados. O único momento em que você tem a senha em texto puro é no login, então é ali que você atualiza:

O prefixo $2y$10$ vira $2y$12$, e no próximo login o password_needs_rehash() retorna false.

Nunca use md5 ou sha1 para senhas

md5() e sha1() são hashes gerais rápidos e sem salt. Rápido é exatamente o errado para senhas: um atacante com uma tabela vazada consegue testar bilhões de palpites por segundo numa GPU, e um hash sem salt de uma senha comum já está em tabelas públicas de consulta. Compare a velocidade:

No tempo em que o password_hash() faz um hash, o md5 faz centenas de milhares, então cada palpite de quem tenta quebrar a senha fica esse tanto de vezes mais barato. Pesquise 5f4dcc3b5aa765d61d8327deb882cf99 e você encontra a palavra "password" na hora.

Se você herdar uma tabela de hashes md5, não dá para convertê-los diretamente. Envolva-os: guarde password_hash($oldMd5) e verifique com password_verify(md5($input), $stored), depois troque cada um por um hash normal quando aquele usuário fizer login de novo.

Argon2id e o limite de 72 bytes

O PASSWORD_ARGON2ID usa mais memória por hash, o que torna a quebra por GPU muito mais cara. Ele está disponível quando o PHP é compilado com a libargon2 (a maioria dos pacotes Linux é); verifique com defined('PASSWORD_ARGON2ID').

A última linha imprime bool(true): duas senhas diferentes batem porque o bcrypt ignora tudo depois do byte 72. Senhas reais raramente chegam a isso, mas frases-senha em japonês ou em outras escritas multibyte chegam a 72 bytes com uns 24 caracteres. Se você permite senhas muito longas, use Argon2id, que não tem esse limite. O password_verify() funciona com os dois algoritmos, então você pode trocar com o password_needs_rehash() como acima.

Perguntas frequentes

Por que o password_hash dá um resultado diferente a cada vez?

Cada chamada gera um novo salt aleatório e o guarda dentro do hash, então a mesma senha dá uma string diferente a cada vez. É intencional: o password_verify() lê o salt de volta do hash guardado, então continua retornando true para a senha certa.

Qual deve ser o tamanho da coluna de senha para o password_hash?

Um hash bcrypt do PASSWORD_DEFAULT tem 60 caracteres hoje, mas o algoritmo padrão pode mudar numa versão futura do PHP. Use VARCHAR(255) para que um hash mais longo caiba sem migração.

Por que o password_verify sempre retorna false?

As causas comuns são um hash truncado (uma coluna VARCHAR(50) corta o hash de 60 caracteres), passar um password_hash() novo de algo como segundo argumento em vez do hash que você guardou, comparar com um hash feito com md5(), ou espaços extras no valor guardado. Verifique se strlen($hash) é 60 e passe a senha em texto puro como primeiro argumento.

Posso descriptografar um valor do password_hash no PHP?

Não. Um hash é de mão única: não existe função que o transforme de volta na senha. Para verificar uma senha, você gera o hash da tentativa e compara, que é o que o password_verify() faz. Para uma senha esquecida, envie um link de redefinição em vez da senha antiga.

Devo usar PASSWORD_DEFAULT, PASSWORD_BCRYPT ou PASSWORD_ARGON2ID?

O PASSWORD_DEFAULT é a escolha comum: hoje é bcrypt e o PHP pode mudá-lo para um algoritmo mais forte depois, o que o password_needs_rehash() deixa você adotar aos poucos. O PASSWORD_ARGON2ID é mais forte contra quebra por GPU e pode ser usado quando o seu PHP o inclui.

Ilustração das linguagens de programação do Coddy

Aprenda a programar com o Coddy

COMEÇAR