O "hash duplo" é uma senha menos segura do que apenas uma vez?

293

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.

Bill the Lizard
fonte
2
Hash(password)e Hash(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 MD5 passworde é distinguível do aleatório. Em vez disso, você deve usar um HMAC. É comprovadamente seguro e é um PRF.
JWW

Respostas:

267

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:

erickson
fonte
68
Criar intencionalmente um algoritmo lento é uma prática aceita quando você tenta impedir ataques de dicionário contra armazenamentos de autenticação comprometidos. A técnica é chamada de "reforço das teclas" ou "alongamento das teclas". Veja en.wikipedia.org/wiki/Key_stretching
17
@RoBorg: não importa o quão lenta é sua implementação, mas quão lenta será a implementação de um invasor: se o hash em si for milhares de vezes mais lento, o invasor levará milhares de vezes mais para forçar a senha com força bruta.
orip 07/12/08
5
Pode-se argumentar que você queira colisões no espaço de 128 bits de 0 a 2 ^ 128-1. Se o espaço de saída 2 ^ 128 do algoritmo de hash for perfeito, teoricamente, você terá apenas uma cifra de substituição com um alfabeto de 2 ^ 128 glifos.
jmucchiello
13
@devin - não é "minha solução", é uma prática amplamente aceita, incorporada em padrões de criptografia baseados em senha como o PKCS # 5 e recomendada por especialistas como Robert Morris. É extremamente escalável, pois a fração do tempo gasto na autenticação de usuários é pequena em um aplicativo legítimo. Só se torna difícil escalar quando seu aplicativo está quebrando senhas - daí a recomendação. Certamente, o espaço de pesquisa de um hash é menor que o de senhas possíveis, mas mesmo um espaço de 128 bits é grande demais para a pesquisa por força bruta. A ameaça de defesa é contra um ataque de dicionário offline.
216 erickson
6
Eu estava me referindo não ao inconveniente para o usuário individual, mas ao estresse que seria exercido no servidor se você tivesse uma grande base de usuários, porque depende da carga da CPU para diminuir o número de solicitações. Isso significa que, se você adicionar mais energia da CPU, estará reduzindo a restrição desses atacantes de força bruta. - No entanto, você está completamente correto sobre a escalabilidade e a prática amplamente aceita. Eu estava errado sobre quase todas as coisas que disse nos meus comentários anteriores. Desculpe :)
DevinB
227

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ê:

$hashed_password1 = md5( md5( plaintext_password ) );
$hashed_password2 = md5( plaintext_password );

Existem duas propriedades fundamentais de uma função hash com as quais estamos preocupados:

  1. Resistência pré-imagem - Dado um hash $h, deve ser difícil encontrar uma mensagem $mcomo essa$h === hash($m)

  2. Resistência à segunda pré-imagem - Dada uma mensagem $m1, deve ser difícil encontrar uma mensagem diferente para $m2quehash($m1) === hash($m2)

  3. Resistência à colisão - deve ser difícil encontrar um par de mensagens ($m1, $m2)que hash($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:

function ourHash($input) {
    $result = 0;
    for ($i = 0; $i < strlen($input); $i++) {
        $result += ord($input[$i]);
    }
    return (string) ($result % 256);
}

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:

var_dump(
    ourHash('abc'), // string(2) "38"
    ourHash('def'), // string(2) "47"
    ourHash('hij'), // string(2) "59"
    ourHash('klm')  // string(2) "68"
);

Agora, vamos ver o que acontece se rodarmos algumas vezes em torno de uma função:

$tests = array(
    "abc",
    "def",
    "hij",
    "klm",
);

foreach ($tests as $test) {
    $hash = $test;
    for ($i = 0; $i < 100; $i++) {
        $hash = ourHash($hash);
    }
    echo "Hashing $test => $hash\n";
}

Isso gera:

Hashing abc => 152
Hashing def => 152
Hashing hij => 155
Hashing klm => 155

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:

Hashing 0 => 48
Hashing 1 => 49
Hashing 2 => 50
Hashing 3 => 51
Hashing 4 => 52
Hashing 5 => 53
Hashing 6 => 54
Hashing 7 => 55
Hashing 8 => 56
Hashing 9 => 57
Hashing 10 => 97
Hashing 11 => 98
Hashing 12 => 99
Hashing 13 => 100
Hashing 14 => 101
Hashing 15 => 102
Hashing 16 => 103
Hashing 17 => 104
Hashing 18 => 105
Hashing 19 => 106
Hashing 20 => 98
Hashing 21 => 99
Hashing 22 => 100
Hashing 23 => 101
Hashing 24 => 102
Hashing 25 => 103
Hashing 26 => 104
Hashing 27 => 105
Hashing 28 => 106
Hashing 29 => 107
Hashing 30 => 99
Hashing 31 => 100
Hashing 32 => 101
Hashing 33 => 102
Hashing 34 => 103
Hashing 35 => 104
Hashing 36 => 105
Hashing 37 => 106
Hashing 38 => 107
Hashing 39 => 108
Hashing 40 => 100
Hashing 41 => 101
Hashing 42 => 102
Hashing 43 => 103
Hashing 44 => 104
Hashing 45 => 105
Hashing 46 => 106
Hashing 47 => 107
Hashing 48 => 108
Hashing 49 => 109
Hashing 50 => 101
Hashing 51 => 102
Hashing 52 => 103
Hashing 53 => 104
Hashing 54 => 105
Hashing 55 => 106
Hashing 56 => 107
Hashing 57 => 108
Hashing 58 => 109
Hashing 59 => 110
Hashing 60 => 102
Hashing 61 => 103
Hashing 62 => 104
Hashing 63 => 105
Hashing 64 => 106
Hashing 65 => 107
Hashing 66 => 108
Hashing 67 => 109
Hashing 68 => 110
Hashing 69 => 111
Hashing 70 => 103
Hashing 71 => 104
Hashing 72 => 105
Hashing 73 => 106
Hashing 74 => 107
Hashing 75 => 108
Hashing 76 => 109
Hashing 77 => 110
Hashing 78 => 111
Hashing 79 => 112
Hashing 80 => 104
Hashing 81 => 105
Hashing 82 => 106
Hashing 83 => 107
Hashing 84 => 108
Hashing 85 => 109
Hashing 86 => 110
Hashing 87 => 111
Hashing 88 => 112
Hashing 89 => 113
Hashing 90 => 105
Hashing 91 => 106
Hashing 92 => 107
Hashing 93 => 108
Hashing 94 => 109
Hashing 95 => 110
Hashing 96 => 111
Hashing 97 => 112
Hashing 98 => 113
Hashing 99 => 114
Hashing 100 => 145
Hashing 101 => 146
Hashing 102 => 147
Hashing 103 => 148
Hashing 104 => 149
Hashing 105 => 150
Hashing 106 => 151
Hashing 107 => 152
Hashing 108 => 153
Hashing 109 => 154
Hashing 110 => 146
Hashing 111 => 147
Hashing 112 => 148
Hashing 113 => 149
Hashing 114 => 150
Hashing 115 => 151
Hashing 116 => 152
Hashing 117 => 153
Hashing 118 => 154
Hashing 119 => 155
Hashing 120 => 147
Hashing 121 => 148
Hashing 122 => 149
Hashing 123 => 150
Hashing 124 => 151
Hashing 125 => 152
Hashing 126 => 153
Hashing 127 => 154
Hashing 128 => 155
Hashing 129 => 156
Hashing 130 => 148
Hashing 131 => 149
Hashing 132 => 150
Hashing 133 => 151
Hashing 134 => 152
Hashing 135 => 153
Hashing 136 => 154
Hashing 137 => 155
Hashing 138 => 156
Hashing 139 => 157
Hashing 140 => 149
Hashing 141 => 150
Hashing 142 => 151
Hashing 143 => 152
Hashing 144 => 153
Hashing 145 => 154
Hashing 146 => 155
Hashing 147 => 156
Hashing 148 => 157
Hashing 149 => 158
Hashing 150 => 150
Hashing 151 => 151
Hashing 152 => 152
Hashing 153 => 153
Hashing 154 => 154
Hashing 155 => 155
Hashing 156 => 156
Hashing 157 => 157
Hashing 158 => 158
Hashing 159 => 159
Hashing 160 => 151
Hashing 161 => 152
Hashing 162 => 153
Hashing 163 => 154
Hashing 164 => 155
Hashing 165 => 156
Hashing 166 => 157
Hashing 167 => 158
Hashing 168 => 159
Hashing 169 => 160
Hashing 170 => 152
Hashing 171 => 153
Hashing 172 => 154
Hashing 173 => 155
Hashing 174 => 156
Hashing 175 => 157
Hashing 176 => 158
Hashing 177 => 159
Hashing 178 => 160
Hashing 179 => 161
Hashing 180 => 153
Hashing 181 => 154
Hashing 182 => 155
Hashing 183 => 156
Hashing 184 => 157
Hashing 185 => 158
Hashing 186 => 159
Hashing 187 => 160
Hashing 188 => 161
Hashing 189 => 162
Hashing 190 => 154
Hashing 191 => 155
Hashing 192 => 156
Hashing 193 => 157
Hashing 194 => 158
Hashing 195 => 159
Hashing 196 => 160
Hashing 197 => 161
Hashing 198 => 162
Hashing 199 => 163
Hashing 200 => 146
Hashing 201 => 147
Hashing 202 => 148
Hashing 203 => 149
Hashing 204 => 150
Hashing 205 => 151
Hashing 206 => 152
Hashing 207 => 153
Hashing 208 => 154
Hashing 209 => 155
Hashing 210 => 147
Hashing 211 => 148
Hashing 212 => 149
Hashing 213 => 150
Hashing 214 => 151
Hashing 215 => 152
Hashing 216 => 153
Hashing 217 => 154
Hashing 218 => 155
Hashing 219 => 156
Hashing 220 => 148
Hashing 221 => 149
Hashing 222 => 150
Hashing 223 => 151
Hashing 224 => 152
Hashing 225 => 153
Hashing 226 => 154
Hashing 227 => 155
Hashing 228 => 156
Hashing 229 => 157
Hashing 230 => 149
Hashing 231 => 150
Hashing 232 => 151
Hashing 233 => 152
Hashing 234 => 153
Hashing 235 => 154
Hashing 236 => 155
Hashing 237 => 156
Hashing 238 => 157
Hashing 239 => 158
Hashing 240 => 150
Hashing 241 => 151
Hashing 242 => 152
Hashing 243 => 153
Hashing 244 => 154
Hashing 245 => 155
Hashing 246 => 156
Hashing 247 => 157
Hashing 248 => 158
Hashing 249 => 159
Hashing 250 => 151
Hashing 251 => 152
Hashing 252 => 153
Hashing 253 => 154
Hashing 254 => 155
Hashing 255 => 156

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:

Hashing 0 => 153
Hashing 1 => 154
Hashing 2 => 155
Hashing 3 => 156
Hashing 4 => 157
Hashing 5 => 158
Hashing 6 => 150
Hashing 7 => 151
Hashing 8 => 152
Hashing 9 => 153
Hashing 10 => 157
Hashing 11 => 158
Hashing 12 => 150
Hashing 13 => 154
Hashing 14 => 155
Hashing 15 => 156
Hashing 16 => 157
Hashing 17 => 158
Hashing 18 => 150
Hashing 19 => 151
Hashing 20 => 158
Hashing 21 => 150
Hashing 22 => 154
Hashing 23 => 155
Hashing 24 => 156
Hashing 25 => 157
Hashing 26 => 158
Hashing 27 => 150
Hashing 28 => 151
Hashing 29 => 152
Hashing 30 => 150
Hashing 31 => 154
Hashing 32 => 155
Hashing 33 => 156
Hashing 34 => 157
Hashing 35 => 158
Hashing 36 => 150
Hashing 37 => 151
Hashing 38 => 152
Hashing 39 => 153
Hashing 40 => 154
Hashing 41 => 155
Hashing 42 => 156
Hashing 43 => 157
Hashing 44 => 158
Hashing 45 => 150
Hashing 46 => 151
Hashing 47 => 152
Hashing 48 => 153
Hashing 49 => 154
Hashing 50 => 155
Hashing 51 => 156
Hashing 52 => 157
Hashing 53 => 158
Hashing 54 => 150
Hashing 55 => 151
Hashing 56 => 152
Hashing 57 => 153
Hashing 58 => 154
Hashing 59 => 155
Hashing 60 => 156
Hashing 61 => 157
Hashing 62 => 158
Hashing 63 => 150
Hashing 64 => 151
Hashing 65 => 152
Hashing 66 => 153
Hashing 67 => 154
Hashing 68 => 155
Hashing 69 => 156
Hashing 70 => 157
Hashing 71 => 158
Hashing 72 => 150
Hashing 73 => 151
Hashing 74 => 152
Hashing 75 => 153
Hashing 76 => 154
Hashing 77 => 155
Hashing 78 => 156
Hashing 79 => 157
Hashing 80 => 158
Hashing 81 => 150
Hashing 82 => 151
Hashing 83 => 152
Hashing 84 => 153
Hashing 85 => 154
Hashing 86 => 155
Hashing 87 => 156
Hashing 88 => 157
Hashing 89 => 158
Hashing 90 => 150
Hashing 91 => 151
Hashing 92 => 152
Hashing 93 => 153
Hashing 94 => 154
Hashing 95 => 155
Hashing 96 => 156
Hashing 97 => 157
Hashing 98 => 158
Hashing 99 => 150
Hashing 100 => 154
Hashing 101 => 155
Hashing 102 => 156
Hashing 103 => 157
Hashing 104 => 158
Hashing 105 => 150
Hashing 106 => 151
Hashing 107 => 152
Hashing 108 => 153
Hashing 109 => 154
Hashing 110 => 155
Hashing 111 => 156
Hashing 112 => 157
Hashing 113 => 158
Hashing 114 => 150
Hashing 115 => 151
Hashing 116 => 152
Hashing 117 => 153
Hashing 118 => 154
Hashing 119 => 155
Hashing 120 => 156
Hashing 121 => 157
Hashing 122 => 158
Hashing 123 => 150
Hashing 124 => 151
Hashing 125 => 152
Hashing 126 => 153
Hashing 127 => 154
Hashing 128 => 155
Hashing 129 => 156
Hashing 130 => 157
Hashing 131 => 158
Hashing 132 => 150
Hashing 133 => 151
Hashing 134 => 152
Hashing 135 => 153
Hashing 136 => 154
Hashing 137 => 155
Hashing 138 => 156
Hashing 139 => 157
Hashing 140 => 158
Hashing 141 => 150
Hashing 142 => 151
Hashing 143 => 152
Hashing 144 => 153
Hashing 145 => 154
Hashing 146 => 155
Hashing 147 => 156
Hashing 148 => 157
Hashing 149 => 158
Hashing 150 => 150
Hashing 151 => 151
Hashing 152 => 152
Hashing 153 => 153
Hashing 154 => 154
Hashing 155 => 155
Hashing 156 => 156
Hashing 157 => 157
Hashing 158 => 158
Hashing 159 => 159
Hashing 160 => 151
Hashing 161 => 152
Hashing 162 => 153
Hashing 163 => 154
Hashing 164 => 155
Hashing 165 => 156
Hashing 166 => 157
Hashing 167 => 158
Hashing 168 => 159
Hashing 169 => 151
Hashing 170 => 152
Hashing 171 => 153
Hashing 172 => 154
Hashing 173 => 155
Hashing 174 => 156
Hashing 175 => 157
Hashing 176 => 158
Hashing 177 => 159
Hashing 178 => 151
Hashing 179 => 152
Hashing 180 => 153
Hashing 181 => 154
Hashing 182 => 155
Hashing 183 => 156
Hashing 184 => 157
Hashing 185 => 158
Hashing 186 => 159
Hashing 187 => 151
Hashing 188 => 152
Hashing 189 => 153
Hashing 190 => 154
Hashing 191 => 155
Hashing 192 => 156
Hashing 193 => 157
Hashing 194 => 158
Hashing 195 => 159
Hashing 196 => 151
Hashing 197 => 152
Hashing 198 => 153
Hashing 199 => 154
Hashing 200 => 155
Hashing 201 => 156
Hashing 202 => 157
Hashing 203 => 158
Hashing 204 => 150
Hashing 205 => 151
Hashing 206 => 152
Hashing 207 => 153
Hashing 208 => 154
Hashing 209 => 155
Hashing 210 => 156
Hashing 211 => 157
Hashing 212 => 158
Hashing 213 => 150
Hashing 214 => 151
Hashing 215 => 152
Hashing 216 => 153
Hashing 217 => 154
Hashing 218 => 155
Hashing 219 => 156
Hashing 220 => 157
Hashing 221 => 158
Hashing 222 => 150
Hashing 223 => 151
Hashing 224 => 152
Hashing 225 => 153
Hashing 226 => 154
Hashing 227 => 155
Hashing 228 => 156
Hashing 229 => 157
Hashing 230 => 158
Hashing 231 => 150
Hashing 232 => 151
Hashing 233 => 152
Hashing 234 => 153
Hashing 235 => 154
Hashing 236 => 155
Hashing 237 => 156
Hashing 238 => 157
Hashing 239 => 158
Hashing 240 => 150
Hashing 241 => 151
Hashing 242 => 152
Hashing 243 => 153
Hashing 244 => 154
Hashing 245 => 155
Hashing 246 => 156
Hashing 247 => 157
Hashing 248 => 158
Hashing 249 => 159
Hashing 250 => 151
Hashing 251 => 152
Hashing 252 => 153
Hashing 253 => 154
Hashing 254 => 155
Hashing 255 => 156

Nós já reduzi-nos para baixo para 8 valores ... Isso é ruim ... A nossa função original mapeado S(∞)para S(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 mapeia S(∞)para S(2^128). Como não há garantia de que a corrida MD5(S(output))será Injetiva , o que significa que não haverá colisões.

Seção TL / DR

Portanto, como alimentar a saída md5diretamente 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 resultados 2^128seja reduzido, ele não é significativamente reduzido com rapidez suficiente para ser uma falha crítica.

Assim,

$output = md5($input); // 2^128 possibilities
$output = md5($output); // < 2^128 possibilities
$output = md5($output); // < 2^128 possibilities
$output = md5($output); // < 2^128 possibilities
$output = md5($output); // < 2^128 possibilities

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:

$output = md5($input); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities    

Observe que as iterações adicionais não são 2 ^ 128 para cada valor individual de $input. Isso significa que podemos gerar $inputvalores que ainda colidem na linha (e, portanto, serão liquidados ou ressoarão em muito menos do que 2^128os resultados possíveis). Mas o argumento geral $inputainda é 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:

Hashing 0 => 201
Hashing 1 => 212
Hashing 2 => 199
Hashing 3 => 201
Hashing 4 => 203
Hashing 5 => 205
Hashing 6 => 207
Hashing 7 => 209
Hashing 8 => 211
Hashing 9 => 204
Hashing 10 => 251
Hashing 11 => 147
Hashing 12 => 251
Hashing 13 => 148
Hashing 14 => 253
Hashing 15 => 0
Hashing 16 => 1
Hashing 17 => 2
Hashing 18 => 161
Hashing 19 => 163
Hashing 20 => 147
Hashing 21 => 251
Hashing 22 => 148
Hashing 23 => 253
Hashing 24 => 0
Hashing 25 => 1
Hashing 26 => 2
Hashing 27 => 161
Hashing 28 => 163
Hashing 29 => 8
Hashing 30 => 251
Hashing 31 => 148
Hashing 32 => 253
Hashing 33 => 0
Hashing 34 => 1
Hashing 35 => 2
Hashing 36 => 161
Hashing 37 => 163
Hashing 38 => 8
Hashing 39 => 4
Hashing 40 => 148
Hashing 41 => 253
Hashing 42 => 0
Hashing 43 => 1
Hashing 44 => 2
Hashing 45 => 161
Hashing 46 => 163
Hashing 47 => 8
Hashing 48 => 4
Hashing 49 => 9
Hashing 50 => 253
Hashing 51 => 0
Hashing 52 => 1
Hashing 53 => 2
Hashing 54 => 161
Hashing 55 => 163
Hashing 56 => 8
Hashing 57 => 4
Hashing 58 => 9
Hashing 59 => 11
Hashing 60 => 0
Hashing 61 => 1
Hashing 62 => 2
Hashing 63 => 161
Hashing 64 => 163
Hashing 65 => 8
Hashing 66 => 4
Hashing 67 => 9
Hashing 68 => 11
Hashing 69 => 4
Hashing 70 => 1
Hashing 71 => 2
Hashing 72 => 161
Hashing 73 => 163
Hashing 74 => 8
Hashing 75 => 4
Hashing 76 => 9
Hashing 77 => 11
Hashing 78 => 4
Hashing 79 => 3
Hashing 80 => 2
Hashing 81 => 161
Hashing 82 => 163
Hashing 83 => 8
Hashing 84 => 4
Hashing 85 => 9
Hashing 86 => 11
Hashing 87 => 4
Hashing 88 => 3
Hashing 89 => 17
Hashing 90 => 161
Hashing 91 => 163
Hashing 92 => 8
Hashing 93 => 4
Hashing 94 => 9
Hashing 95 => 11
Hashing 96 => 4
Hashing 97 => 3
Hashing 98 => 17
Hashing 99 => 13
Hashing 100 => 246
Hashing 101 => 248
Hashing 102 => 49
Hashing 103 => 44
Hashing 104 => 255
Hashing 105 => 198
Hashing 106 => 43
Hashing 107 => 51
Hashing 108 => 202
Hashing 109 => 2
Hashing 110 => 248
Hashing 111 => 49
Hashing 112 => 44
Hashing 113 => 255
Hashing 114 => 198
Hashing 115 => 43
Hashing 116 => 51
Hashing 117 => 202
Hashing 118 => 2
Hashing 119 => 51
Hashing 120 => 49
Hashing 121 => 44
Hashing 122 => 255
Hashing 123 => 198
Hashing 124 => 43
Hashing 125 => 51
Hashing 126 => 202
Hashing 127 => 2
Hashing 128 => 51
Hashing 129 => 53
Hashing 130 => 44
Hashing 131 => 255
Hashing 132 => 198
Hashing 133 => 43
Hashing 134 => 51
Hashing 135 => 202
Hashing 136 => 2
Hashing 137 => 51
Hashing 138 => 53
Hashing 139 => 55
Hashing 140 => 255
Hashing 141 => 198
Hashing 142 => 43
Hashing 143 => 51
Hashing 144 => 202
Hashing 145 => 2
Hashing 146 => 51
Hashing 147 => 53
Hashing 148 => 55
Hashing 149 => 58
Hashing 150 => 198
Hashing 151 => 43
Hashing 152 => 51
Hashing 153 => 202
Hashing 154 => 2
Hashing 155 => 51
Hashing 156 => 53
Hashing 157 => 55
Hashing 158 => 58
Hashing 159 => 0
Hashing 160 => 43
Hashing 161 => 51
Hashing 162 => 202
Hashing 163 => 2
Hashing 164 => 51
Hashing 165 => 53
Hashing 166 => 55
Hashing 167 => 58
Hashing 168 => 0
Hashing 169 => 209
Hashing 170 => 51
Hashing 171 => 202
Hashing 172 => 2
Hashing 173 => 51
Hashing 174 => 53
Hashing 175 => 55
Hashing 176 => 58
Hashing 177 => 0
Hashing 178 => 209
Hashing 179 => 216
Hashing 180 => 202
Hashing 181 => 2
Hashing 182 => 51
Hashing 183 => 53
Hashing 184 => 55
Hashing 185 => 58
Hashing 186 => 0
Hashing 187 => 209
Hashing 188 => 216
Hashing 189 => 219
Hashing 190 => 2
Hashing 191 => 51
Hashing 192 => 53
Hashing 193 => 55
Hashing 194 => 58
Hashing 195 => 0
Hashing 196 => 209
Hashing 197 => 216
Hashing 198 => 219
Hashing 199 => 220
Hashing 200 => 248
Hashing 201 => 49
Hashing 202 => 44
Hashing 203 => 255
Hashing 204 => 198
Hashing 205 => 43
Hashing 206 => 51
Hashing 207 => 202
Hashing 208 => 2
Hashing 209 => 51
Hashing 210 => 49
Hashing 211 => 44
Hashing 212 => 255
Hashing 213 => 198
Hashing 214 => 43
Hashing 215 => 51
Hashing 216 => 202
Hashing 217 => 2
Hashing 218 => 51
Hashing 219 => 53
Hashing 220 => 44
Hashing 221 => 255
Hashing 222 => 198
Hashing 223 => 43
Hashing 224 => 51
Hashing 225 => 202
Hashing 226 => 2
Hashing 227 => 51
Hashing 228 => 53
Hashing 229 => 55
Hashing 230 => 255
Hashing 231 => 198
Hashing 232 => 43
Hashing 233 => 51
Hashing 234 => 202
Hashing 235 => 2
Hashing 236 => 51
Hashing 237 => 53
Hashing 238 => 55
Hashing 239 => 58
Hashing 240 => 198
Hashing 241 => 43
Hashing 242 => 51
Hashing 243 => 202
Hashing 244 => 2
Hashing 245 => 51
Hashing 246 => 53
Hashing 247 => 55
Hashing 248 => 58
Hashing 249 => 0
Hashing 250 => 43
Hashing 251 => 51
Hashing 252 => 202
Hashing 253 => 2
Hashing 254 => 51
Hashing 255 => 53

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 0e3 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 quanto md5($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:

T_1 = Hash (P || S) ,
T_2 = Hash (T_1) ,
...
T_c = Hash (T_{c-1}) 

Onde cestá a contagem de iterações, Pé a Senha eS é o salt

PBKDF2:

U_1 = PRF (P, S || INT (i)) ,
U_2 = PRF (P, U_1) ,
...
U_c = PRF (P, U_{c-1})

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 Hashfunção subjacente , enquanto o PBKDF1 não.

Amarrando Tudo Juntos:

Conhecemos maneiras seguras de iterar um hash. De fato:

$hash = $input;
$i = 10000;
do {
   $hash = hash($input . $hash);
} while ($i-- > 0);

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 consistente S(n). A iteração seguinte (assumindo que a entrada é passado de volta em) mapeia S(∞)para S(n)novamente:

S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)

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 !!!

ircmaxell
fonte
1
$output = md5($output); // < 2^128 possibilities--- é realmente rigoroso <, ou <=?
zerkms
2
@zerkms: Não é estritamente nada. Precisamos conhecer alguns detalhes muito específicos da função subjacente ( 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 $outputpara tudo possível $inputs. Então, se temos mesmo uma colisão será <, portanto, <é a melhor generalizador.
ircmaxell
2
@ TomášFejfar Acho que a questão não é sobre colisões em geral, mas colisões no conjunto estrito de saída (2 ^ 128 saídas, cada uma com exatamente 128 bits de largura). Isso pode ser injetivo, mas até onde eu sei uma prova genérica não é possível (apenas uma prova por exemplo de uma colisão para um algoritmo específico). Considere a função hash que simplesmente retorna a entrada se ela tiver 128 bits (e hashes caso contrário). Em geral, seria surjective, mas quando alimentado sua saída seria sempre injective ... Esse é o ponto de discórdia ...
ircmaxell
3
Vamos continuar esta discussão no chat .
ircmaxell
6
Para aqueles que gostariam de economizar tempo, sem precisar ir verificar como terminou a discussão entre Dan e ircmaxell, terminou bem : Dan concordando com ircmaxell.
jeromej
51

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).

orip
fonte
2
@DFTR - concordou. bcrypt ou scrypt são melhores opções.
orip
Não use os (família SHA-2), pois eles também podem ser facilmente quebrados, verifique crackstation.net para obter provas. Se alguma coisa usar scrypt ou PBKDF2, que são funções hash criptográficas baseadas em função de derivação de chave (KDFs).
theodore
3
Em 2016, Argon2 e Scrypt são os que todos devem se esforçar para usar
silkfire
10

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.

Rich Bradshaw
fonte
5

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).

alecco
fonte
4

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.

Pat
fonte
Esta resposta está errada em todos os aspectos. 1. Saber o penúltimo hash não fornece valor a um invasor, porque a entrada para um hash iterado é a senha , que é hash várias vezes (não uma vez). 2. O espaço de entrada é senhas, o espaço de saída é hash de senhas. O espaço das senhas típicas é muito menor que o espaço de saída. 3. As tabelas arco-íris para senhas com hash duplo sem sal não são maiores que as tabelas arco-íris para senhas com hash único sem sal. 4. Os nomes de usuário são de baixa entropia, um bom sal é aleatório. 5. Salga não substitui a iteração. Você precisa dos dois.
Clement Cherlin
3

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).

Bill Karwin
fonte
Eu mencionei isso em outro comentário também, mas en.wikipedia.org/wiki/Key_stretching
2

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.

$hashed_password = md5( "xxx" + "|" + user_name + "|" + plaintext_password);
CodeAndCats
fonte
13
Na verdade, deve ser uma sequência gerada aleatoriamente para cada usuário, não uma constante.
Bill o Lagarto
7
Um segredo constante funciona (e é mais fácil trabalhar com ele), se você inserir o nome de usuário, conforme sugerido. Isso essencialmente produz uma chave aleatória específica do usuário.
SquareCog 08/12/08
4
Um sal secreto constante é a segurança através da obscuridade. Se o "segredo" revelar que você está usando "xxx" + nome de usuário + senha, um invasor nem precisa de dados de suas tabelas para iniciar um ataque contra ele.
Bill o Lagarto
8
Não acho que seja segurança através da obscuridade. A razão para usar um salt é que você não pode calcular uma tabela arco-íris com vários hashes MD5 simultaneamente. A criação de um para "xxx" + senha (mesmo sal) acontece uma vez. Construir uma tabela para "xxx" + nome de usuário + senha é pior do que a força bruta.
FryGuy
5
@ Bill the Lizard: "o ataque é reduzido à construção de um dicionário para atacar um nome de usuário específico" é apenas um ataque de força bruta (na verdade ainda pior, porque além de computar todos os hashes você precisa armazená-los), o sal funciona perfeitamente neste caso.
Kornel
2

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. .

Ole Tange
fonte
1

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.

SoapBox
fonte
1
Tudo correto, mas quero apenas observar que o efeito do forte comprometimento da resistência à colisão no MD5 é um pouco desproporcional - a maioria dos cenários que usam funções de criptografia não depende de forte resistência à colisão, apenas resistência fraca. Eles não são afetados por esta vulnerabilidade.
SquareCog 07/12/08
1

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:

  1. O primeiro hash no cliente protege seus usuários em um cenário de 'violação do servidor'.
  2. O segundo hash no servidor serve para proteger seu sistema se alguém tiver o backup do seu banco de dados, para que ele não possa usar essas senhas para se conectar aos seus serviços.
Vedran
fonte
1
+1 Eu estava esperando para ver uma resposta como esta, porque pensei no mesmo cenário em que você não deseja armazenar uma senha em texto sem formatação no cliente, mas também não enviar a senha criptografada final por fio para fazer uma comparação simples com o DB.
Mark
1
Não ajuda para aplicativos da web. se o servidor estiver comprometido, o código que o servidor está enviando para o cliente também será comprometido. O invasor desativará o hash do lado do cliente e capturará senhas brutas.
Clement Cherlin
0

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.

SquareCog
fonte
1
Re-hash é precisamente reduzir a velocidade do hash. Esse é um recurso de segurança essencial na criptografia baseada em senha. Veja os links para PCKS5 e PBKDF2.
orip 8/12/08
0

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.

Stefan Rusek
fonte
Isso não responde à pergunta.
Bill the Lizard
1
O hash duplo não vale o esforço, mas é dobrar o tamanho do hash. Eu acho que esse é um ponto mais valioso.
Stefan Rusek
-1

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.

Sargun Dhillon
fonte
-1

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.

hobbs
fonte
-1

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.

Greg
fonte