Como o bcrypt pode ter sais embutidos?

617

O artigo de Coda Hale "Como armazenar com segurança uma senha" afirma que:

O bcrypt possui sais embutidos para evitar ataques à tabela do arco-íris.

Ele cita este artigo , que diz que na implementação do OpenBSD de bcrypt:

O OpenBSD gera o sal bcrypt de 128 bits a partir de um fluxo de chaves arcfour (arc4random (3)), semeado com dados aleatórios que o kernel coleta a partir das temporizações do dispositivo.

Eu não entendo como isso pode funcionar. Na minha concepção de sal:

  • Ele precisa ser diferente para cada senha armazenada, para que uma tabela arco-íris separada precise ser gerada para cada senha armazenada.
  • Ele precisa ser armazenado em algum lugar para que possa ser repetido: quando um usuário tenta fazer login, tentamos a senha, repetimos o mesmo procedimento de sal e hash que fizemos quando armazenamos a senha originalmente e comparamos

Quando estou usando o Devise (um gerenciador de login do Rails) com o bcrypt, não há coluna salt no banco de dados, então estou confuso. Se o sal for aleatório e não for armazenado em nenhum lugar, como podemos repetir com segurança o processo de mistura?

Em resumo, como o bcrypt pode ter sais embutidos ?

Nathan Long
fonte

Respostas:

789

Isto é bcrypt:

Gere um sal aleatório. Um fator de "custo" foi pré-configurado. Colete uma senha.

Derive uma chave de criptografia da senha usando o fator de custo e sal. Use-o para criptografar uma sequência conhecida. Armazene o custo, o sal e o texto cifrado. Como esses três elementos têm um comprimento conhecido, é fácil concatená-los e armazená-los em um único campo, e ainda assim poder separá-los mais tarde.

Quando alguém tentar se autenticar, recupere o custo e o sal armazenados. Derive uma chave da senha de entrada, custo e sal. Criptografe a mesma sequência conhecida. Se o texto cifrado gerado corresponder ao texto cifrado armazenado, a senha será uma correspondência.

O Bcrypt opera de maneira muito semelhante aos esquemas mais tradicionais baseados em algoritmos como PBKDF2. A principal diferença é o uso de uma chave derivada para criptografar texto sem formatação conhecido; outros esquemas (razoavelmente) assumem que a função de derivação de chave é irreversível e armazenam a chave derivada diretamente.


Armazenado no banco de dados, um bcrypt"hash" pode ser algo como isto:

$ 2a $ 10 $ vI8aWBnW3fID.ZQ4 / zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa

Na verdade, são três campos, delimitados por "$":

  • 2aidentifica a bcryptversão do algoritmo que foi usada.
  • 10é o fator de custo; 2 10 iterações da função de derivação de chave são usadas (o que não é suficiente, a propósito. Eu recomendaria um custo de 12 ou mais.)
  • vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTaé o sal e o texto cifrado, concatenados e codificados em uma Base-64 modificada. Os primeiros 22 caracteres decodificam para um valor de 16 bytes para o sal. Os caracteres restantes são texto cifrado a ser comparado para autenticação.

Este exemplo é retirado da documentação para implementação em ruby ​​de Coda Hale.

erickson
fonte
7
Você teria mais detalhes sobre por que o fator de custo 10 não seria suficiente? No Grails, notei que 10 é o valor padrão para rodadas de fator de custo / log para bcrypt, portanto vale a pena atualizar, de acordo com sua sugestão.
Pm_labs 14/05
57
O fator de custo para bcrypt é exponencial, ou melhor, um fator de custo de 10 significa 2 ^ 10 rodadas (1024), um fator de custo de 16 significaria 2 ^ 16 rodadas (65536). É natural, então, que levaria de 5 a 10 segundos. Deve levar cerca de 64 vezes o tempo que um fator de custo de 10 faz. Para esclarecer outras informações erradas, a função de criptografia do PHP usa a biblioteca de criptografia unix que é implementada em c.
thomasrutter
3
@TJChambers Isso mesmo; se você pode definir a senha na conta, poderá se autenticar. O hash de senha não se destina a impedir esse ataque. Ele visa impedir que um invasor com acesso somente leitura à tabela de senhas seja autenticado. Por exemplo, você recebe uma fita de backup com a tabela nela.
22614
8
@LobsterMan Não, na verdade não. Se você pudesse manter um segredo, não usaria essa abordagem, apenas armazenaria a senha. Os esquemas de autenticação de senha são baseados no pressuposto de que o invasor descobriu tudo o que você sabe. O salt está lá para exigir que cada senha seja atacada individualmente. O esforço computacional necessário para testar senhas é governado pelas iterações. Se os usuários escolherem boas senhas, eles estarão seguros, mesmo quando o sal for revelado. Esconder o sal poderia ajudar alguém com uma senha incorreta em alguns casos, mas eu trabalharia primeiro na qualidade da senha.
Erickson
1
@NLV É uma string definida na especificação bcrypt:"OrpheanBeholderScryDoubt"
erickson
182

Acredito que essa frase deveria ter sido redigida da seguinte forma:

O bcrypt possui sais embutidos nos hashes gerados para evitar ataques à tabela do arco-íris.

O bcryptutilitário em si não parece manter uma lista de sais. Em vez disso, os sais são gerados aleatoriamente e anexados à saída da função, para que sejam lembrados posteriormente (de acordo com a implementação Java debcrypt ). Em outras palavras, o "hash" gerado por bcryptnão é apenas o hash. Pelo contrário, é o hash e o sal concatenados.

Adam Paynter
fonte
20
OK, então me inscrevo em um site e escolha a senha "foo". Bcryptadiciona um sal aleatório de "akd2! *", resultando em "fooakd2! *", que é hash e armazenado. Mais tarde, tento entrar com a senha "bar". Para ver se estou correto, ele precisa do hash "barakd2! *". Se o sal foi gerado aleatoriamente, como saber como adicioná-lo novamente a "bar" antes de fazer o hash e comparar?
Nathan Long
46
@ Nathan: bcryptsabe como extrair o sal da saída gerada (que é armazenada no banco de dados). Quando chega a hora de autenticar, bcryptsepara a saída original em seus componentes hash e salt. O componente salt é aplicado à senha de entrada digitada pelo usuário.
Adam Paynter
22
Para responder ao comentário de Nathan Long, uma boa maneira de pensar sobre isso é que os sais não devem ser secretos. É por isso que o sal é incluído na saída da função bcrypt como uma das respostas apontadas acima. O salt está lá para impedir tabelas arco-íris, que são listas de senhas comuns, ou apenas força bruta, etc ... de senhas diferentes, mas com hash. Sem salt, o hash para uma senha no banco de dados A seria o mesmo que um hash para uma senha no banco de dados B. Salt apenas altera os valores de hash, tornando mais difícil para alguém que roubou o banco de dados descriptografar senhas (unhash).
Joseph Astrahan
11
@ Nathan, mas um invasor pode apenas remover os sais conhecidos em todas as senhas e criar uma tabela com eles?
Oscar
3
É assim que eu entendo: a idéia é que toda senha tenha um sal único. O sal é incorporado no hash da senha, para que um hacker precise criar uma tabela arco-íris para cada senha. Isso levaria uma quantidade enorme de tempo para um banco de dados moderado. É tudo sobre diminuir a velocidade de um atacante e, assim, tornar inútil a força bruta.
PVermeer 02/04
0

Para tornar as coisas ainda mais claras,

Direção de registro / login ->

A senha + salt é criptografada com uma chave gerada a partir de: cost, salt e a senha. chamamos esse valor criptografado de cipher text. em seguida, anexamos o salt a esse valor e o codificamos usando base64. anexando o custo a ele e esta é a sequência produzida a partir de bcrypt:

$2a$COST$BASE64

Este valor é armazenado eventualmente.

O que o invasor precisaria fazer para encontrar a senha? (outra direção <-)

Caso o invasor tenha controle sobre o banco de dados, o invasor decodificará facilmente o valor base64 e poderá ver o sal. o sal não é secreto. embora seja aleatório. Então ele precisará descriptografar o arquivo cipher text.

O que é mais importante: não há hash nesse processo, mas criptografia cara da CPU - descriptografia. portanto, as tabelas arco-íris são menos relevantes aqui.

jony89
fonte
-2

Isso é da documentação da interface PasswordEncoder da Spring Security,

 * @param rawPassword the raw password to encode and match
 * @param encodedPassword the encoded password from storage to compare with
 * @return true if the raw password, after encoding, matches the encoded password from
 * storage
 */
boolean matches(CharSequence rawPassword, String encodedPassword);

O que significa que você precisará corresponder ao rawPassword que o usuário digitará novamente no próximo login e corresponderá à senha codificada em Bcrypt, armazenada no banco de dados durante o login / registro anterior.

Conheça Shah
fonte
Isso não responde à pergunta ... Não diz nada sobre como o bcrypt pode ter sais
embutidos