Como posso armazenar as senhas dos meus usuários com segurança?

169

Quão mais seguro é esse do que o MD5 comum ? Acabei de começar a investigar a segurança da senha. Eu sou muito novo em PHP.

$salt = 'csdnfgksdgojnmfnb';

$password = md5($salt.$_POST['password']);
$result = mysql_query("SELECT id FROM users
                       WHERE username = '".mysql_real_escape_string($_POST['username'])."'
                       AND password = '$password'");

if (mysql_num_rows($result) < 1) {
    /* Access denied */
    echo "The username or password you entered is incorrect.";
} 
else {
    $_SESSION['id'] = mysql_result($result, 0, 'id');
    #header("Location: ./");
    echo "Hello $_SESSION[id]!";
}
Vergalhões
fonte
Note que o php 5.4+ está embutido
Benjamin Gruenbaum
Veja também a estrutura de hash de senha PHP do Openwall (PHPass). É portátil e reforçado contra uma série de ataques comuns a senhas de usuários.
JWW
1
Obrigatório "use DOP em vez de interpolação de cordas", para as pessoas que hoje tropeçam nessa questão.
Fund Monica's Lawsuit

Respostas:

270

A maneira mais fácil de proteger seu esquema de armazenamento de senhas é usando uma biblioteca padrão .

Como a segurança tende a ser muito mais complicada e com mais possibilidades de confusão invisíveis do que a maioria dos programadores poderia enfrentar sozinha, o uso de uma biblioteca padrão é quase sempre a opção mais fácil e segura (se não a única) disponível.


A nova API de senha do PHP (5.5.0+)

Se você estiver usando o PHP versão 5.5.0 ou mais recente, poderá usar a nova API de hash de senha simplificada

Exemplo de código usando a API de senha do PHP:

<?php
// $hash is what you would store in your database
$hash = password_hash($_POST['password'], PASSWORD_DEFAULT, ['cost' => 12]);

// $hash would be the $hash (above) stored in your database for this user
$checked = password_verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

(Caso você ainda esteja usando o legado 5.3.7 ou mais recente, você pode instalar o ircmaxell / password_compat para ter acesso às funções internas )


Melhorando os hashes salgados: adicione pimenta

Se você deseja segurança extra, o pessoal de segurança agora (2017) recomenda adicionar uma ' pimenta ' aos hashes de senha com sal (automaticamente).

Há uma queda simples na classe que implementa esse padrão com segurança, eu recomendo: Netsilik / PepperedPasswords ( github ).
Ele vem com uma licença MIT, para que você possa usá-lo como quiser, mesmo em projetos proprietários.

Exemplo de código usando Netsilik/PepperedPasswords:

<?php
use Netsilik/Lib/PepperedPasswords;

// Some long, random, binary string, encoded as hexadecimal; stored in your configuration (NOT in your Database, as that would defeat the entire purpose of the pepper).
$config['pepper'] = hex2bin('012345679ABCDEF012345679ABCDEF012345679ABCDEF012345679ABCDEF');

$hasher = new PepperedPasswords($config['pepper']);

// $hash is what you would store in your database
$hash = $hasher->hash($_POST['password']);

// $hash would be the $hash (above) stored in your database for this user
$checked = $hasher->verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}


A biblioteca padrão OLD

Atenção: você não precisa mais disso! Isto é apenas aqui para fins históricos.

Dê uma olhada em: Estrutura portátil de hash de senha PHP : phpass e certifique-se de usar o CRYPT_BLOWFISHalgoritmo, se possível.

Exemplo de código usando phpass (v0.2):

<?php
require('PasswordHash.php');

$pwdHasher = new PasswordHash(8, FALSE);

// $hash is what you would store in your database
$hash = $pwdHasher->HashPassword( $password );

// $hash would be the $hash (above) stored in your database for this user
$checked = $pwdHasher->CheckPassword($password, $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

O PHPass foi implementado em alguns projetos bastante conhecidos:

  • phpBB3
  • WordPress 2.5+ e bbPress
  • a versão do Drupal 7 (módulo disponível para o Drupal 5 e 6)
  • outras

O bom é que você não precisa se preocupar com os detalhes, esses detalhes foram programados por pessoas com experiência e revisados ​​por muitas pessoas na internet.

Para obter mais informações sobre esquemas de armazenamento de senhas, leia a postagem no blog de Jeff : Você provavelmente está armazenando senhas incorretamente

Faça o que fizer, se você vai para o ' eu vou fazer isso sozinho, obrigado abordagem', não use MD5ou SHA1mais . Eles são um bom algoritmo de hash, mas são considerados quebrados por questões de segurança .

Atualmente, o uso da cripta , com CRYPT_BLOWFISH é a melhor prática.
CRYPT_BLOWFISH no PHP é uma implementação do hash Bcrypt. O Bcrypt é baseado na cifra de bloco Blowfish, usando sua configuração de chave dispendiosa para retardar o algoritmo.

Jacco
fonte
29

Seus usuários ficarão muito mais seguros se você usar consultas parametrizadas em vez de concatenar instruções SQL. E o salt deve ser exclusivo para cada usuário e deve ser armazenado junto com o hash da senha.

Anton Gogolev
fonte
1
Há um bom artigo sobre segurança em PHP no Nettuts +, além de mencionar a salga de senha. Talvez você deva dar uma olhada em: net.tutsplus.com/tutorials/php/…
Fábio Antunes
3
O Nettuts + é um artigo muito ruim para ser usado como modelo - inclui o uso do MD5, que pode ser forçado com força bruta com muita facilidade, mesmo com sal. Em vez disso, basta usar a biblioteca PHPass que é muito, muito melhor do que qualquer código que você pode encontrar em um site tutorial, ou seja, esta resposta: stackoverflow.com/questions/1581610/...
RichVel
11

Uma maneira melhor seria que cada usuário tivesse um sal único.

A vantagem de ter sal é que dificulta ao invasor pré-gerar a assinatura MD5 de cada palavra do dicionário. Mas se um invasor descobrir que você tem um salt fixo, ele poderá gerar previamente a assinatura MD5 de cada palavra do dicionário prefixada pelo seu salt fixo.

Uma maneira melhor é sempre que um usuário altera sua senha, seu sistema gera um sal aleatório e armazena esse sal junto com o registro do usuário. Torna um pouco mais caro verificar a senha (já que você precisa procurar o sal antes de poder gerar a assinatura MD5), mas torna muito mais difícil para um invasor pré-gerar MD5.

R Samuel Klatchko
fonte
3
Os sais são geralmente armazenados junto com o hash da senha (por exemplo, a saída da crypt()função). E como você precisa recuperar o hash da senha, usar um salt específico do usuário não tornará o procedimento mais caro. (Ou você quis dizer que gerar um novo sal aleatório é caro? Acho que não.) Caso contrário, +1.
Inshallah
Por motivos de segurança, convém fornecer acesso à tabela apenas por meio de procedimentos armazenados e impedir que o hash seja retornado. Em vez disso, o cliente passa o que acha que é o hash e recebe um sinalizador de sucesso ou falha. Isso permite que o processo armazenado registre a tentativa, crie uma sessão etc.
Steven Sudit
@Inshallah - se todos os usuários tiverem o mesmo sal, você poderá reutilizar o ataque do dicionário usado no usuário1 contra o usuário2. Mas se cada usuário tiver um sal exclusivo, você precisará gerar um novo dicionário para cada usuário que deseja atacar.
R Samuel Klatchko 25/03
@R Samuel - foi exatamente por isso que votei na sua resposta, porque recomenda a estratégia de melhores práticas para evitar esses ataques. Meu comentário pretendia expressar minha perplexidade com o que você disse sobre o custo adicional de um sal por usuário, que eu não entendi. (uma vez que "sais são normalmente armazenados em conjunto com o hash de senha" quaisquer requisitos de armazenamento e CPU adicionais para um sal por usuário são tão microscópico, que não precisa sequer ser mencionado ...)
Inshallah
@ Inshallah - Eu estava pensando no caso em que você tem o banco de dados verificado se a senha do hash está correta (então você tem uma recuperação de banco de dados para obter o salt e um segundo acesso ao banco de dados para verificar a senha do hash). Você está certo quanto ao download da senha salt / hash em uma única recuperação e, em seguida, faz a comparação no cliente. Desculpe pela confusão.
R Samuel Klatchko 28/03/10
11

Com o PHP 5.5 (o que eu descrevo está disponível para versões anteriores, veja abaixo) ao virar da esquina, eu gostaria de sugerir o uso de sua nova solução embutida: password_hash()e password_verify(). Ele fornece várias opções para atingir o nível de segurança de senha que você precisa (por exemplo, especificando um parâmetro "cost" através da $optionsmatriz)

<?php
var_dump(password_hash("my-secret-password", PASSWORD_DEFAULT));

$options = array(
    'cost' => 7, // this is the number of rounds for bcrypt
    // 'salt' => 'TphfsM82o1uEKlfP9vf1f', // you could specify a salt but it is not recommended
);
var_dump(password_hash("my-secret-password", PASSWORD_BCRYPT, $options));
?>

retornará

string(60) "$2y$10$w2LxXdIcqJpD6idFTNn.eeZbKesdu5y41ksL22iI8C4/6EweI7OK."
string(60) "$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d."

Como você pode ver, a cadeia contém o sal, bem como o custo especificado nas opções. Ele também contém o algoritmo usado.

Portanto, ao verificar a senha (por exemplo, quando o usuário faz login), ao usar a password_verify()função complementar , ele extrai os parâmetros criptográficos necessários do próprio hash da senha.

Quando não for especificado um salt, o hash da senha gerada será diferente a cada chamada password_hash()porque o salt é gerado aleatoriamente. Portanto, a comparação de um hash anterior com um recém-gerado falhará, mesmo para uma senha correta.

A verificação funciona assim:

var_dump(password_verify("my-secret-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));
var_dump(password_verify("wrong-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));

var_dump(password_verify("my-secret-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));
var_dump(password_verify("wrong-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));

Espero que o fornecimento dessas funções internas em breve forneça melhor segurança de senha em caso de roubo de dados, pois reduz a quantidade de pensamento que o programador precisa colocar em uma implementação adequada.

Existe uma pequena biblioteca (um arquivo PHP) que fornece PHP 5.5 password_hashno PHP 5.3.7+: https://github.com/ircmaxell/password_compat

akirk
fonte
2
Na maioria dos casos, é melhor omitir o parâmetro salt. A função cria um sal a partir da fonte aleatória do sistema operacional; há muito pouca chance de você poder fornecer um sal melhor sozinho.
martinstoeckli
1
Foi o que eu escrevi, não foi? "se não for especificado de sal, que é gerada de forma aleatória, por essa razão é preferível não especificar um sal"
akirk
A maioria dos exemplos mostra como adicionar os dois parâmetros, mesmo quando não é recomendável adicionar sal, então, por que? E para ser sincero, li apenas o comentário por trás do código, não na próxima linha. De qualquer forma, não seria melhor quando o exemplo mostra como usar melhor a função?
martinstoeckli
Você está certo, eu concordo. Mudei minha resposta de acordo e comentei a linha. Graças
akirk
como eu deve verificar se a senha salva ea senha digitados são o mesmo que eu estou usando? password_hash()e password_verifynão importa o que a senha (correta ou não) eu usei eu acabar com senha correta
Brownman Revival
0

Por mim tudo bem. O Sr. Atwood escreveu sobre a força do MD5 contra as mesas do arco-íris e, basicamente, com um sal comprido como esse, você fica bem (embora alguns números / pontuação aleatórios possam melhorar).

Você também pode olhar para o SHA-1, que parece estar ficando mais popular atualmente.

nickf
fonte
6
A nota na parte inferior da postagem do Sr. Atwood (em vermelho) é vinculada a outra postagem de um profissional de segurança que declara usar MD5, SHA1 e outros hashes rápidos para armazenar senhas está muito errado.
Sipwiz 17/10/09
2
@ Matthew Scharley: Não concordo que o esforço adicional imposto por algoritmos caros de hash de senha seja uma segurança falsa. É para evitar brutalidade de senhas fáceis de adivinhar. Se você está limitando as tentativas de login, está se protegendo contra a mesma coisa (embora um pouco mais eficaz). Porém, se um adversário tiver acesso aos hashes armazenados no banco de dados, ele poderá forçar com força bruta essas senhas (facilmente adivinhadas) com bastante rapidez (dependendo da facilidade de adivinhação). O padrão para o algoritmo de criptografia SHA-256 é 10000 redondo, o que tornaria 10000 vezes mais difícil.
Inshallah
3
Os hashes lentos são realmente feitos repetindo-se um número muito rápido de vezes, e embaralhando os dados entre cada iteração. O objetivo é garantir que, mesmo que o bandido receba uma cópia de seus hashes de senha, ele tenha que gastar uma quantidade considerável de tempo da CPU para testar seu dicionário nos seus hashes.
18/10/09
4
@caf: Eu acredito que o algoritmo bcrypt utiliza o custo parametrizável do agendamento das teclas Eksblowfish; não tem muita certeza de como isso funciona, mas o agendamento de chaves geralmente é uma operação muito cara feita durante o objeto de contexto de cifra init, antes de qualquer criptografia.
Inshallah
3
Inshallah: Isso é verdade - o algoritmo bcrypt é um design diferente, onde o primitivo criptográfico subjacente é uma cifra de bloco e não uma função de hash. Eu estava me referindo a esquemas baseados em funções de hash, como o MD5 do PHK crypt ().
18/10/09
0

Eu quero adicionar:

  • Não limite senhas de usuários por tamanho

Para compatibilidade com sistemas antigos, defina frequentemente um limite para o comprimento máximo da senha. Esta é uma política de segurança incorreta: se você definir restrições, defina-as apenas para o comprimento mínimo de senhas.

  • Não envie senhas de usuário por email

Para recuperar uma senha esquecida, você deve enviar o endereço pelo qual o usuário pode alterar a senha.

  • Atualize os hashes das senhas dos usuários

O hash da senha pode estar desatualizado (os parâmetros do algoritmo podem ser atualizados). Usando a função, password_needs_rehash()você pode conferir.


fonte