Como fazer hash de senhas longas (> 72 caracteres) com baiacu

91

Na semana passada, li muitos artigos sobre hashing de senha e Blowfish parece ser (um dos) melhores algoritmos de hashing agora - mas esse não é o assunto desta pergunta!

O limite de 72 caracteres

Blowfish considera apenas os primeiros 72 caracteres na senha inserida:

<?php
$password = "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)";
$hash = password_hash($password, PASSWORD_BCRYPT);
var_dump($password);

$input = substr($password, 0, 72);
var_dump($input);

var_dump(password_verify($input, $hash));
?>

O resultado é:

string(119) "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)"
string(72) "Wow. This is a super secret and super, super long password. Let's add so"
bool(true)

Como você pode ver, apenas os primeiros 72 caracteres são importantes. O Twitter está usando blowfish aka bcrypt para armazenar suas senhas ( https://shouldichangemypassword.com/twitter-hacked.php ) e adivinhe: mude sua senha do Twitter para uma senha longa com mais de 72 caracteres e você pode fazer login em sua conta por inserindo apenas os primeiros 72 caracteres.

Baiacu e pimenta

Existem muitas opiniões diferentes sobre "salpicar" senhas. Algumas pessoas dizem que é desnecessário, porque você tem que assumir que a string secreta também é conhecida / publicada, de modo que não aprimora o hash. Eu tenho um servidor de banco de dados separado, então é bem possível que apenas o banco de dados esteja vazando e não a pimenta constante.

Neste caso (pimenta não vazou) você torna um ataque baseado em um dicionário mais difícil (me corrija se não estiver certo). Se o seu barbante de pimenta também vazou: nada mal - você ainda tem o sal e está tão bem protegido quanto um haxixe sem pimenta.

Portanto, acho que definir a senha pelo menos não é uma escolha ruim.

Sugestão

Minha sugestão para obter um hash Blowfish para uma senha com mais de 72 caracteres (e pimenta) é:

<?php
$pepper = "foIwUVmkKGrGucNJMOkxkvcQ79iPNzP5OKlbIdGPCMTjJcDYnR";

// Generate Hash
$password = "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)";
$password_peppered = hash_hmac('sha256', $password, $pepper);
$hash = password_hash($password_peppered, PASSWORD_BCRYPT);

// Check
$input = substr($password, 0, 72);
$input_peppered = hash_hmac('sha256', $input, $pepper);

var_dump(password_verify($input_peppered, $hash));
?>

Isso se baseia nesta pergunta : password_verifyretornar false.

A questão

Qual é a maneira mais segura? Obter um hash SHA-256 primeiro (que retorna 64 caracteres) ou considerar apenas os primeiros 72 caracteres da senha?

Prós

  • O usuário não pode fazer o login inserindo apenas os primeiros 72 caracteres
  • Você pode adicionar pimenta sem exceder o limite de caracteres
  • A saída de hash_hmac provavelmente teria mais entropia do que a própria senha
  • A senha é hash por duas funções diferentes

Contras

  • Apenas 64 caracteres são usados ​​para construir o hash baiacu


Edit 1: Esta questão aborda apenas a integração PHP do blowfish / bcrypt. Obrigado pelos comentários!

Frederik Kammer
fonte
3
Blowfish não é o único que trunca a senha, enganando as pessoas a pensarem que é mais seguro do que realmente é. Aqui está uma história interessante do limite de 8 caracteres.
DOK de
2
O truncamento de 72 caracteres é fundamental para o algoritmo Blowfish ou apenas a implementação do PHP? IIRC Blowfish também é usado em (pelo menos alguns) 'nixes para criptografar as senhas do usuário.
Douglas B. Staple de
3
O problema é com Bcrypt, não Blowfish. Posso reproduzir esse problema apenas com Python e Bcrypt.
Blender
@Blender: Obrigado por seu comentário e seu trabalho nele. Não consegui encontrar funções diferentes em php para baiacu e bcrypt, embora sejam iguais. Mas isso não faz diferença para mim em php? Eu preferiria usar a função php padrão.
Frederik Kammer
1
Consulte também a estrutura de hash de senha do Openwall (PHPass). É portátil e resistente a vários ataques comuns a senhas de usuários. O cara que escreveu o framework (SolarDesigner) é o mesmo cara que escreveu John The Ripper e é jurado na Competição de Hashing de Senha . Portanto, ele sabe uma ou duas coisas sobre ataques a senhas.
jww

Respostas:

134

O problema aqui é basicamente um problema de entropia. Então, vamos começar a procurar lá:

Entropia por personagem

O número de bits de entropia por byte são:

  • Personagens hexadecimais
    • Bits: 4
    • Valores: 16
    • Entropia em 72 caracteres: 288 bits
  • Alfa-numérico
    • Bits: 6
    • Valores: 62
    • Entropia em 72 caracteres: 432 bits
  • Símbolos "Comuns"
    • Bits: 6,5
    • Valores: 94
    • Entropia em 72 caracteres: 468 bits
  • Bytes completos
    • Bits: 8
    • Valores: 255
    • Entropia em 72 caracteres: 576 bits

Então, como agimos depende do tipo de personagem que esperamos.

O primeiro problema

O primeiro problema com seu código é que sua etapa de hash "pepper" está gerando caracteres hexadecimais (já que o quarto parâmetro para hash_hmac()não está definido).

Portanto, ao inserir a pimenta, você está efetivamente cortando a entropia máxima disponível para a senha por um fator de 2 (de 576 a 288 bits possíveis ).

O segundo problema

No entanto, em primeiro lugar , sha256fornece apenas 256bits de entropia. Portanto, você está efetivamente reduzindo possíveis 576 bits para 256 bits. Seu hash step * imediatamente *, por definição perde pelo menos 50% da entropia possível na senha.

Você poderia resolver isso parcialmente mudando para SHA512, onde reduziria a entropia disponível em cerca de 12%. Mas essa ainda é uma diferença não insignificante. Esses 12% reduzem o número de permutações por um fator de 1.8e19. É um grande número ... E esse é o fator que o reduz em ...

O problema subjacente

O problema subjacente é que existem três tipos de senhas com mais de 72 caracteres. O impacto que este sistema de estilo tem sobre eles será muito diferente:

Nota: de agora em diante, estou assumindo que estamos comparando a um sistema de pimenta que usa SHA512com saída bruta (não hex).

  • Senhas aleatórias de alta entropia

    Estes são seus usuários usando geradores de senha que geram a quantidade de chaves grandes para senhas. Eles são aleatórios (gerados, não escolhidos por humanos) e têm alta entropia por personagem. Esses tipos usam bytes altos (caracteres> 127) e alguns caracteres de controle.

    Para este grupo, sua função de hashing reduzirá significativamente a entropia disponível em bcrypt.

    Deixe-me dizer isso de novo. Para usuários que usam senhas longas e de alta entropia, sua solução reduz significativamente a força de suas senhas em uma quantidade mensurável. (62 bits de entropia perdidos para uma senha de 72 caracteres e mais para senhas mais longas)

  • Senhas aleatórias de entropia média

    Este grupo está usando senhas contendo símbolos comuns, mas sem bytes altos ou caracteres de controle. Estas são suas senhas digitáveis.

    Para este grupo, você irá desbloquear um pouco mais entropia (não criá-la, mas permitir que mais entropia caiba na senha bcrypt). Quando digo levemente, quero dizer levemente. O ponto de equilíbrio ocorre quando você atinge o máximo de 512 bits do SHA512. Portanto, o pico é de 78 caracteres.

    Deixe-me dizer isso de novo. Para esta classe de senhas, você só pode armazenar 6 caracteres adicionais antes de esgotar a entropia.

  • Senhas não aleatórias de baixa entropia

    Este é o grupo que está usando caracteres alfanuméricos que provavelmente não são gerados aleatoriamente. Algo como uma citação da Bíblia ou algo assim. Essas frases têm aproximadamente 2,3 bits de entropia por caractere.

    Para este grupo, você pode desbloquear significativamente mais entropia (não criá-la, mas permitir que mais entropia caiba na entrada de senha bcrypt) por hash. O ponto de equilíbrio é de cerca de 223 caracteres antes de esgotar a entropia.

    Vamos dizer isso de novo. Para essa classe de senhas, o pré-hashing definitivamente aumenta a segurança de forma significativa.

De volta ao mundo real

Esses tipos de cálculos de entropia não importam muito no mundo real. O que importa é adivinhar a entropia. Isso é o que afeta diretamente o que os invasores podem fazer. Isso é o que você deseja maximizar.

Embora haja pouca pesquisa para adivinhar a entropia, há alguns pontos que gostaria de apontar.

As chances de adivinhar aleatoriamente 72 caracteres corretos em uma linha são extremamente baixas. É mais provável que você ganhe na loteria Powerball 21 vezes do que essa colisão ... Esse é o número de que estamos falando.

Mas podemos não tropeçar nisso estatisticamente. No caso de frases, a chance dos primeiros 72 caracteres serem iguais é muito maior do que para uma senha aleatória. Mas ainda é trivialmente baixo (é mais provável que você ganhe na loteria Powerball 5 vezes, com base em 2,3 bits por personagem).

Praticamente

Praticamente, isso realmente não importa. As chances de alguém adivinhar os primeiros 72 caracteres corretamente, onde os últimos fazem uma diferença significativa, são tão baixas que não vale a pena se preocupar. Por quê?

Bem, digamos que você esteja pegando uma frase. Se a pessoa conseguir acertar os primeiros 72 caracteres, ela tem muita sorte (pouco provável) ou é uma frase comum. Se for uma frase comum, a única variável é quanto tempo deve ser produzida.

Vamos dar um exemplo. Vamos fazer uma citação da Bíblia (só porque é uma fonte comum de textos longos, não por qualquer outro motivo):

Você não deve cobiçar a casa do seu vizinho. Não cobiçarás a mulher do teu vizinho, nem o seu servo ou serva, o seu boi ou jumento, ou qualquer coisa que pertença ao teu vizinho.

São 180 caracteres. O 73º personagem é go segundo neighbor's. Se você adivinhou isso, provavelmente não está parando em nei, mas continuando com o resto do versículo (já que é assim que a senha provavelmente será usada). Portanto, seu "hash" não acrescentou muito.

BTW: ABSOLUTAMENTE NÃO estou defendendo o uso de uma citação da Bíblia. Na verdade, exatamente o oposto.

Conclusão

Você não vai ajudar muito as pessoas que usam senhas longas fazendo hash primeiro. Alguns grupos você definitivamente pode ajudar. Alguns você definitivamente pode machucar.

Mas no final, nada disso é excessivamente significativo. Os números com os quais estamos lidando são MUITO altos. A diferença na entropia não será muito.

É melhor você deixar bcrypt como está. É mais provável que você estrague o hashing (literalmente, você já fez isso e não é o primeiro ou o último a cometer esse erro) do que o ataque que está tentando evitar vai acontecer.

Concentre-se em proteger o resto do site. E adicione um medidor de entropia de senha à caixa de senha no registro para indicar a força da senha (e indicar se uma senha é longa demais para que o usuário queira alterá-la) ...

Isso é meu $ 0,02 pelo menos (ou possivelmente bem mais que $ 0,02) ...

Quanto a usar uma pimenta "secreta":

Não há literalmente nenhuma pesquisa sobre alimentar uma função hash em bcrypt. Portanto, não está claro, na melhor das hipóteses, se alimentar um hash "apimentado" em bcrypt algum dia causará vulnerabilidades desconhecidas (sabemos que isso hash1(hash2($value))pode expor vulnerabilidades significativas em relação à resistência à colisão e ataques de pré-imagem).

Considerando que você já está pensando em armazenar uma chave secreta (a "pimenta"), por que não usá-la de uma forma bem estudada e compreendida? Por que não criptografar o hash antes de armazená-lo?

Basicamente, depois de fazer o hash da senha, coloque toda a saída do hash em um algoritmo de criptografia forte. Em seguida, armazene o resultado criptografado.

Agora, um ataque de injeção de SQL não vazará nada de útil, porque eles não têm a chave de criptografia. E se a chave vazar, os invasores não estarão em melhor situação do que se você usasse um hash simples (o que é provável, algo com a pimenta "pré-hash" não fornece).

Nota: se você decidir fazer isso, use uma biblioteca. Para PHP, eu recomendo fortemente o Zend\Cryptpacote do Zend Framework 2 . Na verdade, é o único que eu recomendaria neste momento. Foi fortemente revisto e toma todas as decisões por você (o que é uma coisa muito boa) ...

Algo como:

use Zend\Crypt\BlockCipher;

public function createHash($password) {
    $hash = password_hash($password, PASSWORD_BCRYPT, ["cost"=>$this->cost]);

    $blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
    $blockCipher->setKey($this->key);
    return $blockCipher->encrypt($hash);
}

public function verifyHash($password, $hash) {
    $blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
    $blockCipher->setKey($this->key);
    $hash = $blockCipher->decrypt($hash);

    return password_verify($password, $hash);
}

E é benéfico porque você está usando todos os algoritmos de maneiras que são bem compreendidas e bem estudadas (pelo menos relativamente). Lembrar:

Qualquer pessoa, do amador mais desinformado ao melhor criptógrafo, pode criar um algoritmo que ele mesmo não consegue quebrar.

ircmaxell
fonte
6
Muito obrigado por esta resposta detalhada. Isso realmente me ajuda!
Frederik Kammer
1
Meu elogio por essa resposta. Um pequeno detalhe, porém, é a grande maioria dos usuários, que usa senhas muito fracas, palavras e derivados contidos em um dicionário para quebrar senhas, uma pimenta os protegeria independentemente de questões de entrofia. Para evitar a perda de entrofia, você pode apenas concatenar a senha e a pimenta. No entanto, sua sugestão sobre criptografar o valor de hash é provavelmente a melhor solução para adicionar um segredo do lado do servidor.
martinstoeckli
2
@martinstoeckli: Meu problema com o conceito de pimenta não está no valor dela. É nisso que a aplicação da "pimenta" entra em território desconhecido em termos de algoritmos criptográficos. Isso não é bom. Em vez disso, acredito que as primitivas criptográficas devam ser combinadas de uma forma que foram projetadas para ficarem juntas. Basicamente, o conceito central de pimenta soa aos meus ouvidos como se algumas pessoas que não sabiam nada sobre criptografia dissessem "Mais hashes é melhor! Temos sal, pimenta também é bom!" . Prefiro um
implemento
@ircmaxell - Sim, conheço seu ponto de vista e concordo, contanto que os valores de hash sejam criptografados posteriormente. Se você não realizar essa etapa adicional, um ataque de dicionário simplesmente revelará muitas senhas fracas, mesmo com um bom algoritmo de hash.
martinstoeckli
@martinstoeckli: Eu discordo disso. Armazenar segredos não é algo trivial. Em vez disso, se você usar bcrypt com um bom custo (12 hoje), todas as senhas, exceto a classe mais fraca, serão seguras (dicionário e as senhas triviais são as mais fracas). Então, eu prefiro recomendar que as pessoas se concentrem em educar o usuário com medidores de força e fazê-los usar senhas melhores em primeiro lugar ...
ircmaxell
5

Melhorar as senhas é certamente uma boa coisa a se fazer, mas vamos ver por quê.

Primeiro devemos responder à pergunta quando exatamente uma pimenta ajuda. A pimenta protege apenas as senhas, desde que permaneça secreta, portanto, se um invasor tiver acesso ao próprio servidor, não adianta. Um ataque muito mais fácil é a injeção de SQL, que permite acesso de leitura ao banco de dados (para nossos valores de hash), eu preparei uma demonstração de injeção de SQL para mostrar como pode ser fácil (clique na próxima seta para obter um entrada).

Então, o que a pimenta realmente ajuda? Enquanto a pimenta permanecer secreta, ela protege as senhas fracas de um ataque de dicionário. A senha 1234se tornaria algo assim 1234-p*deDIUZeRweretWy+.O. Esta senha não só é muito mais longa, como também contém caracteres especiais e nunca fará parte de nenhum dicionário.

Agora podemos estimar quais senhas nossos usuários usarão, provavelmente mais usuários digitarão senhas fracas, pois há usuários com senhas entre 64-72 caracteres (na verdade, isso será muito raro).

Outro ponto é o alcance da força bruta. A função hash sha256 retornará 256 bits de saída ou combinações de 1.2E77, isso é demais para força bruta, mesmo para GPU (se eu calculasse corretamente, isso precisaria de cerca de 2E61 anos em uma GPU em 2013). Portanto, não temos uma desvantagem real ao aplicar a pimenta. Como os valores hash não são sistemáticos, você não pode acelerar a força bruta com padrões comuns.

PS Pelo que eu sei, o limite de 72 caracteres é específico do algoritmo do próprio BCrypt. A melhor resposta que encontrei é esta .

PPS Acho que seu exemplo está falho, você não pode gerar o hash com o comprimento total da senha e verificar com um truncado. Você provavelmente pretendia aplicar a pimenta da mesma maneira para gerar o hash e para verificar o hash.

martinstoeckli
fonte
Quanto ao seu PPS, posso apenas dizer: Sim, ele pode verificar a senha truncada com o hash da não truncada e ainda obter true. É disso que trata esta questão. Dê uma olhada: viper-7.com/RLKFnB
Sliq 01 de
@Panique - O problema não é o cálculo do hash BCrypt, é o HMAC antes. Para gerar o hash SHA, o OP usa a senha de comprimento total e usa o resultado como entrada para BCrypt. Para verificação, ele trunca a senha antes de calcular o hash SHA e, em seguida, usa esse resultado completamente diferente como entrada para BCrypt. O HMAC aceita entrada de qualquer comprimento.
martinstoeckli
2

Bcrypt usa um algoritmo baseado no caro algoritmo de configuração de chave Blowfish.

O limite de senha de 56 bytes recomendado (incluindo byte de terminação nulo) para bcrypt está relacionado ao limite de 448 bits da chave Blowfish. Quaisquer bytes além desse limite não são totalmente misturados ao hash resultante. O limite absoluto de 72 bytes em senhas bcrypt é, portanto, menos relevante, quando você considera o efeito real no hash resultante por esses bytes.

Se você acha que seus usuários normalmente escolheriam senhas com mais de 55 bytes de comprimento, lembre-se de que você sempre pode aumentar as rodadas de extensão de senha, para aumentar a segurança no caso de violação da tabela de senha (embora isso deva ser muito comparado com adicionar extras personagens). Se os direitos de acesso dos usuários forem tão críticos que normalmente exigiriam uma senha muito longa, a expiração da senha também deveria ser curta, como 2 semanas. Isso significa que é muito menos provável que uma senha permaneça válida enquanto um hacker investe seus recursos para derrotar o fator de trabalho envolvido no teste de cada senha de teste para ver se ela produzirá um hash correspondente.

Claro, no caso de a tabela de senhas não ser violada, devemos permitir que os hackers, no máximo, dez tentativas de adivinhar a senha de 55 bytes de um usuário, antes de bloquear a conta do usuário;)

Se você decidir fazer o pré-hash de uma senha com mais de 55 bytes, deverá usar SHA-384, pois tem a maior saída sem ultrapassar o limite.

Phil
fonte
1
"a expiração da senha também deve ser curta, como 2 semanas" de "senhas extremamente longas", sério, por que se preocupar em salvar a senha então, basta usar a redefinição de senha todas as vezes. Sério, essa é a solução errada, mude para a autenticação de dois fatores com um token.
zaph
Obrigado @zaph. Você pode me apontar um exemplo disso? Parece interessante.
Phil
[DRAFT NIST Special Publication 800-63B Digital Authentication Guideline] ( pages.nist.gov/800-63-3/sp800-63b.html ), 5.1.1.2. Verificadores de Segredos Memorizados: Os verificadores NÃO DEVEM exigir que os segredos memorizados sejam alterados arbitrariamente (por exemplo, periodicamente) . Consulte também Para melhores requisitos de senha, de Jim Fenton.
zaph
1
O fato é que quanto mais frequentemente um usuário é solicitado a alterar uma senha, piores se tornam as opções de senha, reduzindo assim a segurança. O usuário tem uma quantidade limitada de boas senhas memorizáveis ​​e elas acabam, escolhendo senhas realmente ruins ou escrevendo-as em post-
its