O hash de uma senha duas vezes antes do armazenamento é mais ou menos seguro do que apenas uma vez?
O que eu estou falando é fazer isso:
$hashed_password = hash(hash($plaintext_password));
em vez de apenas isso:
$hashed_password = hash($plaintext_password);
Se for menos seguro, você pode fornecer uma boa explicação (ou um link para um)?
Além disso, a função hash usada faz diferença? Faz alguma diferença se você misturar md5 e sha1 (por exemplo) em vez de repetir a mesma função hash?
Nota 1: Quando digo "hash duplo", estou falando de hash de uma senha duas vezes, na tentativa de torná-la mais obscura. Não estou falando da técnica para resolver colisões .
Nota 2: Eu sei que preciso adicionar um sal aleatório para torná-lo realmente seguro. A questão é se o hash duas vezes com o mesmo algoritmo ajuda ou prejudica o hash.
security
hash
passwords
cryptography
password-hash
Bill the Lizard
fonte
fonte
Hash(password)
eHash(Hash(password))
são igualmente inseguros. Ambos não têm a noção de segurança semântica . Ou seja, a saída é distinguível da aleatória. Por exemplo,MD5("password")
é5f4dcc3b5aa765d61d8327deb882cf99
. Eu sei que é o hash MD5password
e é distinguível do aleatório. Em vez disso, você deve usar um HMAC. É comprovadamente seguro e é um PRF.Respostas:
Hashing de uma senha uma vez é inseguro
Não, vários hashes não são menos seguros; eles são uma parte essencial do uso seguro de senha.
A iteração do hash aumenta o tempo necessário para que um invasor tente cada senha em sua lista de candidatos. Você pode facilmente aumentar o tempo necessário para atacar uma senha de horas para anos.
A iteração simples não é suficiente
Apenas encadear a saída de hash na entrada não é suficiente para segurança. A iteração deve ocorrer no contexto de um algoritmo que preserva a entropia da senha. Felizmente, existem vários algoritmos publicados que tiveram escrutínio suficiente para dar confiança em seu design.
Um bom algoritmo de derivação de chave como PBKDF2 injeta a senha em cada rodada de hash, atenuando preocupações sobre colisões na saída de hash. PBKDF2 pode ser usado para autenticação de senha como está. Bcrypt segue a derivação de chave com uma etapa de criptografia; Dessa forma, se uma maneira rápida de reverter a derivação de chave for descoberta, um invasor ainda precisará concluir um ataque de texto sem formatação conhecido.
Como quebrar uma senha
As senhas armazenadas precisam de proteção contra ataques offline. Se as senhas não forem salgadas, elas poderão ser quebradas com um ataque de dicionário pré-calculado (por exemplo, usando uma Tabela Arco-Íris). Caso contrário, o invasor deve gastar tempo para calcular um hash para cada senha e verificar se ele corresponde ao hash armazenado.
Todas as senhas não são igualmente prováveis. Os invasores podem pesquisar exaustivamente todas as senhas curtas, mas sabem que suas chances de sucesso na força bruta diminuem acentuadamente com cada caractere adicional. Em vez disso, eles usam uma lista ordenada das senhas mais prováveis. Eles começam com "password123" e avançam para senhas usadas com menos frequência.
Digamos que uma lista de invasores seja longa, com 10 bilhões de candidatos; suponha também que um sistema de desktop possa calcular 1 milhão de hashes por segundo. O invasor pode testar sua lista inteira em menos de três horas se apenas uma iteração for usada. Mas se apenas 2000 iterações forem usadas, esse tempo se estende para quase 8 meses. Para derrotar um invasor mais sofisticado - um capaz de baixar um programa que pode aproveitar o poder de sua GPU, por exemplo - você precisa de mais iterações.
Quanto é suficiente?
O número de iterações a serem usadas é uma troca entre segurança e experiência do usuário. O hardware especializado que pode ser usado pelos atacantes é barato, mas ainda pode executar centenas de milhões de iterações por segundo. O desempenho do sistema do invasor determina quanto tempo leva para quebrar uma senha, devido a várias iterações. Mas é provável que seu aplicativo não use esse hardware especializado. Quantas iterações você pode executar sem agravar os usuários depende do seu sistema.
Você provavelmente pode permitir que os usuários esperem mais um segundo ou mais durante a autenticação. Perfile sua plataforma de destino e use quantas iterações puder. As plataformas que eu testei (um usuário em um dispositivo móvel ou muitos usuários em uma plataforma de servidor) podem suportar confortavelmente PBKDF2 com entre 60.000 e 120.000 iterações ou bcrypt com fator de custo de 12 ou 13.
Mais plano de fundo
Leia o PKCS # 5 para obter informações oficiais sobre o papel do sal e das iterações no hash. Embora o PBKDF2 tenha sido criado para gerar chaves de criptografia a partir de senhas, ele funciona bem como um hash unidirecional para autenticação de senha. Cada iteração de bcrypt é mais cara que um hash SHA-2, então você pode usar menos iterações, mas a idéia é a mesma. O Bcrypt também vai um passo além da maioria das soluções baseadas em PBKDF2, usando a chave derivada para criptografar um texto simples conhecido. O texto cifrado resultante é armazenado como "hash", junto com alguns metadados. No entanto, nada impede você de fazer o mesmo com o PBKDF2.
Aqui estão outras respostas que escrevi sobre este tópico:
fonte
Para aqueles que dizem que é seguro, eles estão corretos em geral . O hash "duplo" (ou a expansão lógica disso, iterando uma função de hash) é absolutamente seguro se feito corretamente , para uma preocupação específica.
Para aqueles que dizem que é inseguro, eles estão corretos neste caso . O código publicado na pergunta é inseguro. Vamos falar sobre o porquê:
Existem duas propriedades fundamentais de uma função hash com as quais estamos preocupados:
Resistência pré-imagem - Dado um hash
$h
, deve ser difícil encontrar uma mensagem$m
como essa$h === hash($m)
Resistência à segunda pré-imagem - Dada uma mensagem
$m1
, deve ser difícil encontrar uma mensagem diferente para$m2
quehash($m1) === hash($m2)
Resistência à colisão - deve ser difícil encontrar um par de mensagens
($m1, $m2)
quehash($m1) === hash($m2)
(observe que isso é semelhante à resistência da segunda pré-imagem, mas diferente, pois aqui o atacante tem controle sobre as duas mensagens) ...Para o armazenamento de senhas , tudo o que realmente importa é a resistência à pré-imagem . Os outros dois seriam discutíveis, porque
$m1
é a senha do usuário que estamos tentando manter em segurança. Portanto, se o atacante já o tiver, o hash não terá nada a proteger ...AVISO LEGAL
Tudo o que se segue é baseado na premissa de que nos preocupamos apenas com a resistência à pré-imagem . As outras duas propriedades fundamentais das funções de hash podem não (e normalmente não) se sustentam da mesma maneira. Portanto, as conclusões deste post são aplicáveis apenas ao usar funções hash para armazenamento de senhas. Eles não são aplicáveis em geral ...
Vamos começar
Para o propósito desta discussão, vamos inventar nossa própria função de hash:
Agora deve ser bastante óbvio o que essa função hash faz. Ele soma os valores ASCII de cada caractere de entrada e, em seguida, assume o módulo desse resultado com 256.
Então, vamos testá-lo:
Agora, vamos ver o que acontece se rodarmos algumas vezes em torno de uma função:
Isso gera:
Uau, uau. Geramos colisões !!! Vamos tentar analisar o porquê:
Aqui está a saída do hash de uma sequência de cada saída de hash possível:
Observe a tendência para números mais altos. Isso acaba sendo o nosso impasse. Executar o hash 4 vezes ($ hash = ourHash ($ hash) `, para cada elemento) acaba dando-nos:
Nós já reduzi-nos para baixo para 8 valores ... Isso é ruim ... A nossa função original mapeado
S(∞)
paraS(256)
. Foi para isso que criamos um mapeamento de Função Surjetiva$input
para$output
.Como temos uma função Surjective, não temos garantia de que o mapeamento de qualquer subconjunto da entrada não tenha colisões (na verdade, na prática, elas terão).
Foi o que aconteceu aqui! Nossa função era ruim, mas não foi por isso que funcionou (é por isso que funcionou tão rápida e completamente).
A mesma coisa acontece com
MD5
. Ele mapeiaS(∞)
paraS(2^128)
. Como não há garantia de que a corridaMD5(S(output))
será Injetiva , o que significa que não haverá colisões.Seção TL / DR
Portanto, como alimentar a saída
md5
diretamente pode gerar colisões, toda iteração aumentará a chance de colisões. Entretanto, este é um aumento linear, o que significa que, embora o conjunto de resultados2^128
seja reduzido, ele não é significativamente reduzido com rapidez suficiente para ser uma falha crítica.Assim,
Quanto mais vezes você itera, mais a redução aumenta.
O conserto
Felizmente para nós, há uma maneira trivial de corrigir isso: alimente algo nas iterações adicionais:
Observe que as iterações adicionais não são 2 ^ 128 para cada valor individual de
$input
. Isso significa que podemos gerar$input
valores que ainda colidem na linha (e, portanto, serão liquidados ou ressoarão em muito menos do que2^128
os resultados possíveis). Mas o argumento geral$input
ainda é tão forte quanto em uma única rodada.Espera, foi? Vamos testar isso com a nossa
ourHash()
função. Alternando para$hash = ourHash($input . $hash);
, para 100 iterações:Ainda existe um padrão aproximado, mas observe que ele não é mais um padrão do que a nossa função subjacente (que já era bastante fraca).
Observe, porém, que
0
e3
se tornou colisão, mesmo que não estivesse na mesma corrida. Essa é uma aplicação do que eu disse antes (que a resistência à colisão permanece a mesma para o conjunto de todas as entradas, mas rotas de colisão específicas podem se abrir devido a falhas no algoritmo subjacente).Seção TL / DR
Ao alimentar a entrada em cada iteração, efetivamente quebramos quaisquer colisões que possam ter ocorrido na iteração anterior.
Portanto,
md5($input . md5($input));
deve ser ( pelo menos teoricamente ) tão forte quantomd5($input)
.Isso é importante?
Sim. Esse é um dos motivos pelos quais o PBKDF2 substituiu o PBKDF1 no RFC 2898 . Considere os loops internos dos dois ::
PBKDF1:
Onde
c
está a contagem de iterações,P
é a Senha eS
é o saltPBKDF2:
Onde PRF é realmente apenas um HMAC. Mas, para nossos propósitos aqui, digamos apenas que
PRF(P, S) = Hash(P || S)
(isto é, o PRF de 2 entradas é o mesmo, grosso modo, como hash com as duas concatenadas juntas). É muito não , mas para os nossos propósitos é.Portanto, o PBKDF2 mantém a resistência à colisão da
Hash
função subjacente , enquanto o PBKDF1 não.Amarrando Tudo Juntos:
Conhecemos maneiras seguras de iterar um hash. De fato:
Normalmente é seguro.
Agora, para explicar por que gostaríamos de fazer o hash, vamos analisar o movimento da entropia.
Um hash recebe o conjunto infinito:
S(∞)
e produz um conjunto menor e de tamanho consistenteS(n)
. A iteração seguinte (assumindo que a entrada é passado de volta em) mapeiaS(∞)
paraS(n)
novamente:Observe que a saída final tem exatamente a mesma quantidade de entropia que a primeira . A iteração não "tornará mais obscurecida". A entropia é idêntica. Não há fonte mágica de imprevisibilidade (é uma função pseudo-aleatória, não uma função aleatória).
No entanto, há um ganho na iteração. Isso torna o processo de hash artificialmente mais lento. E é por isso que iterar pode ser uma boa ideia. De fato, é o princípio básico dos algoritmos mais modernos de hash de senhas (o fato de fazer algo repetidamente o torna mais lento).
Lento é bom, porque está combatendo a principal ameaça à segurança: força bruta. Quanto mais lento for o algoritmo de hash, mais agressivos terão que trabalhar para atacar os hashes de senhas roubados de nós. E isso é uma coisa boa !!!
fonte
$output = md5($output); // < 2^128 possibilities
--- é realmente rigoroso<
, ou<=
?md5()
neste caso) para realmente ter certeza. Mas, em geral será<
e não<=
... Lembre-se, nós estamos falando sobre o tamanho do conjunto de$output
para tudo possível$inputs
. Então, se temos mesmo uma colisão será<
, portanto,<
é a melhor generalizador.Sim, o re-hash reduz o espaço de pesquisa, mas não, isso não importa - a redução efetiva é insignificante.
Re-hash aumenta o tempo necessário para a força bruta, mas fazer isso apenas duas vezes também é subótimo.
O que você realmente deseja é fazer o hash da senha com PBKDF2 - um método comprovado de usar um hash seguro com sal e iterações. Confira esta resposta SO .
EDIT : Eu quase esqueci - NÃO USE MD5 !!!! Use um hash criptográfico moderno, como a família SHA-2 (SHA-256, SHA-384 e SHA-512).
fonte
Sim - reduz o número de possíveis sequências que correspondem à sequência.
Como você já mencionou, os hashes salgados são muito melhores.
Um artigo aqui: http://websecurity.ro/blog/2007/11/02/md5md5-vs-md5/ , tenta uma prova de por que é equivalente, mas não tenho certeza com a lógica. Em parte, eles assumem que não há software disponível para analisar o md5 (md5 (texto)), mas obviamente é bastante trivial produzir as tabelas do arco-íris.
Ainda estou mantendo minha resposta de que há um número menor de hashes do tipo md5 (md5 (texto)) do que os hashes md5 (texto), aumentando a chance de colisão (mesmo que ainda com uma probabilidade improvável) e reduzindo o espaço de pesquisa.
fonte
A maioria das respostas é de pessoas sem formação em criptografia ou segurança. E eles estão errados. Use um sal, se possível exclusivo por registro. MD5 / SHA / etc são muito rápidos, o oposto do que você deseja. PBKDF2 e bcrypt são mais lentos (o que é bom), mas podem ser derrotados com ASICs / FPGA / GPUs (hoje em dia muito acessíveis). Portanto, é necessário um algoritmo com memória insuficiente : digite scrypt .
Aqui está uma explicação leiga sobre sais e velocidade (mas não sobre algoritmos com memória difícil).
fonte
Eu apenas olho para isso do ponto de vista prático. O que é o hacker depois? Por que, a combinação de caracteres que, quando inserida na função hash, gera o hash desejado.
Você está salvando apenas o último hash; portanto, o hacker precisa apenas aplicar força em um hash. Supondo que você tenha aproximadamente as mesmas chances de encontrar o hash desejado a cada passo da força bruta, o número de hashes é irrelevante. Você poderia fazer um milhão de iterações de hash e isso não aumentaria nem reduziria a segurança nem um pouco, pois no final da linha ainda há apenas um hash a ser quebrado, e as chances de quebrá-lo são as mesmas que qualquer hash.
Talvez os pôsteres anteriores pensem que a entrada é relevante; não é. Contanto que o que você colocar na função hash gere o hash desejado, ele fornecerá informações corretas ou incorretas.
Agora, as tabelas do arco-íris são outra história. Como uma tabela arco-íris carrega apenas senhas brutas, o hash duas vezes pode ser uma boa medida de segurança, pois uma tabela arco-íris que contém todos os hash de todos os hash seria muito grande.
Obviamente, estou apenas considerando o exemplo que o OP deu, onde é apenas uma senha de texto sem formatação sendo hash. Se você incluir o nome de usuário ou um sal no hash, é uma história diferente; o hash duas vezes é totalmente desnecessário, pois a tabela rainbow já seria muito grande para ser prática e conter o hash correto.
Enfim, não é um especialista em segurança aqui, mas é exatamente isso que eu descobri na minha experiência.
fonte
Pelo que li, pode ser recomendável re-hash da senha centenas ou milhares de vezes.
A idéia é que, se você puder levar mais tempo para codificar a senha, será mais trabalhoso para um invasor executar várias tentativas de decifrar a senha. Essa parece ser a vantagem do re-hash - não que seja mais criptograficamente seguro, mas simplesmente leva mais tempo para gerar um ataque de dicionário.
É claro que os computadores ficam mais rápidos o tempo todo, portanto essa vantagem diminui com o tempo (ou requer que você aumente as iterações).
fonte
Pessoalmente, eu não me incomodaria com vários hashes, mas também me certificaria de fazer o hash do nome de usuário (ou outro campo de ID do usuário) e da senha, para que dois usuários com a mesma senha não terminem com o mesmo hash. Também eu provavelmente jogaria outra string constante na string de entrada também para uma boa medida.
fonte
Vamos supor que você use o algoritmo de hash: comput rot13, use os 10 primeiros caracteres. Se você fizer isso duas vezes (ou mesmo 2000 vezes), é possível criar uma função que seja mais rápida, mas que dê o mesmo resultado (ou seja, apenas pegue os 10 primeiros caracteres).
Da mesma forma, pode ser possível criar uma função mais rápida que produz a mesma saída que uma função de hash repetida. Portanto, sua escolha da função de hash é muito importante: como no exemplo rot13, não é dado que o hash repetido melhore a segurança. Se não houver pesquisas dizendo que o algoritmo foi projetado para uso recursivo, é mais seguro supor que ele não oferecerá proteção adicional.
Dito isto: Para todas as funções, exceto as mais simples de hash, é provável que os especialistas em criptografia calculem as funções mais rápidas; portanto, se você estiver se protegendo contra invasores que não têm acesso a especialistas em criptografia, provavelmente é mais seguro na prática usar uma função de hash repetida. .
fonte
Em geral, ele não fornece segurança adicional para duplicar o hash ou criptografar algo. Se você pode quebrar o hash uma vez, pode quebrá-lo novamente. Porém, geralmente não prejudica a segurança.
No seu exemplo de uso do MD5, como você provavelmente sabe, existem alguns problemas de colisão. "Double Hashing" não ajuda realmente a proteger contra isso, pois as mesmas colisões ainda resultam no mesmo primeiro hash, que você pode usar no MD5 novamente para obter o segundo hash.
Isso protege contra ataques de dicionário, como os "bancos de dados MD5 reversos", mas a salga também.
Em uma tangente, algo com criptografia dupla não fornece nenhuma segurança adicional porque tudo o que faz é resultar em uma chave diferente, que é uma combinação das duas chaves realmente usadas. Portanto, o esforço para encontrar a "chave" não é dobrado porque duas chaves não precisam realmente ser encontradas. Isso não é verdade para o hash, porque o resultado do hash geralmente não é do mesmo tamanho que a entrada original.
fonte
O hash duplo faz sentido para mim somente se eu fizer o hash da senha no cliente e, em seguida, salvar o hash (com sal diferente) desse hash no servidor.
Dessa forma, mesmo que alguém tenha invadido o servidor (ignorando a segurança que o SSL oferece), ele ainda não consegue acessar as senhas claras.
Sim, ele terá os dados necessários para invadir o sistema, mas não poderá usá-los para comprometer contas externas que o usuário possui. E sabe-se que as pessoas usam a mesma senha para praticamente qualquer coisa.
A única maneira de obter as senhas claras é instalar um keygen no cliente - e esse não é mais o seu problema.
Então, resumindo:
fonte
A preocupação em reduzir o espaço de pesquisa é matematicamente correta, embora o espaço de pesquisa permaneça grande o suficiente para todos os propósitos práticos (supondo que você use sais), em 2 ^ 128. No entanto, como estamos falando de senhas, o número possível de cadeias de caracteres de 16 caracteres (alfanumérico, maiúsculas e minúsculas, alguns símbolos jogados) é de aproximadamente 2 ^ 98, de acordo com meus cálculos de volta ao envelope. Portanto, a diminuição percebida no espaço de pesquisa não é realmente relevante.
Além disso, realmente não há diferença, criptograficamente falando.
Embora exista uma primitiva criptográfica chamada "cadeia de hash" - uma técnica que permite que você faça alguns truques interessantes, como divulgar uma chave de assinatura após ser usada, sem sacrificar a integridade do sistema - devido à sincronização de tempo mínima, isso permite que você evite o problema da distribuição inicial de chaves. Basicamente, você pré-calcula um grande conjunto de hashes de hashes - h (h (h (h .... (h (k)) ...))), usa o enésimo valor para assinar, após um intervalo definido, você envia a chave e assine-a usando a tecla (n-1). Os destinatários agora podem verificar se você enviou todas as mensagens anteriores e ninguém pode falsificar sua assinatura desde que o período para o qual ela é válida passou.
Re-hash centenas de milhares de vezes como Bill sugere é apenas um desperdício de sua CPU. Use uma chave mais longa se estiver preocupado com pessoas que quebram 128 bits.
fonte
Como sugerem várias respostas neste artigo, há alguns casos em que isso pode melhorar a segurança e outros em que a prejudica definitivamente. Existe uma solução melhor que melhorará definitivamente a segurança. Em vez de dobrar o número de vezes que você calcula o hash, o dobro do tamanho do seu sal ou o número de bits usados no hash, ou faça as duas coisas! Em vez do SHA-245, salte para o SHA-512.
fonte
O hash duplo é feio porque é mais do que provável que um invasor tenha construído uma tabela para criar a maioria dos hashes. Melhor é salgar seus hashes e misturar hashes juntos. Existem também novos esquemas para "assinar" hashes (basicamente salgando), mas de maneira mais segura.
fonte
Sim.
Absolutamente não use várias iterações de uma função hash convencional, como
md5(md5(md5(password)))
. Na melhor das hipóteses, você obterá um aumento marginal na segurança (um esquema como esse dificilmente oferece proteção contra um ataque de GPU; basta direcioná-lo.) Na pior das hipóteses, você está reduzindo seu espaço de hash (e, portanto, segurança) a cada iteração adicionada . Em segurança, é aconselhável assumir o pior.Não use uma senha que foi projetado por um criptógrafo competente para ser um hash de senha eficaz e resistente tanto de força bruta e ataques de tempo-espaço. Isso inclui bcrypt, scrypt e, em algumas situações, PBKDF2. O hash baseado em glibc SHA-256 também é aceitável.
fonte
Eu vou sair em um membro e dizer que é mais seguro em determinadas circunstâncias ... não me diminua ainda!
Do ponto de vista matemático / criptográfico, é menos seguro, por razões que tenho certeza de que alguém lhe dará uma explicação mais clara do que eu poderia.
Contudo , existem grandes bancos de dados de hashes MD5, com maior probabilidade de conter o texto "senha" do que o MD5. Portanto, usando o hash duplo, você reduz a eficácia desses bancos de dados.
Obviamente, se você usar sal, essa vantagem (desvantagem?) Desaparece.
fonte