Cómo codificar contraseñas largas (> 72 caracteres) con blowfish

91

La semana pasada leí muchos artículos sobre el hash de contraseñas y Blowfish parece ser (uno de) los mejores algoritmos de hash en este momento, ¡pero ese no es el tema de esta pregunta!

El límite de 72 caracteres

Blowfish solo considera los primeros 72 caracteres en la contraseña ingresada:

<?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));
?>

La salida es:

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 puede ver, solo importan los primeros 72 caracteres. Twitter está utilizando blowfish, también conocido como bcrypt, para almacenar sus contraseñas ( https://shouldichangemypassword.com/twitter-hacked.php ) y adivina qué: cambia tu contraseña de Twitter a una contraseña larga con más de 72 caracteres y puedes iniciar sesión en tu cuenta mediante ingresando solo los primeros 72 caracteres.

Pez globo y pimienta

Hay muchas opiniones diferentes sobre las contraseñas de "pimienta". Algunas personas dicen que es innecesario, porque hay que asumir que el hilo de pimienta secreto también es conocido / publicado, por lo que no mejora el hash. Tengo un servidor de base de datos separado, por lo que es muy posible que solo se filtre la base de datos y no la pimienta constante.

En este caso (pimienta no filtrada) haces más difícil un ataque basado en un diccionario (corrígeme si esto no es correcto). Si su hilo de pimienta también tiene una fuga: no está tan mal, todavía tiene la sal y está tan bien protegida como un picadillo sin pimienta.

Así que creo que modificar la contraseña no es una mala elección.

Sugerencia

Mi sugerencia para obtener un hash Blowfish para una contraseña con más de 72 caracteres (y pimienta) es:

<?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));
?>

Esto se basa en esta pregunta : password_verifyregreso false.

La pregunta

¿Cuál es la forma más segura? ¿Obtener primero un hash SHA-256 (que devuelve 64 caracteres) o considerar solo los primeros 72 caracteres de la contraseña?

Pros

  • El usuario no puede iniciar sesión ingresando solo los primeros 72 caracteres
  • Puede agregar la pimienta sin exceder el límite de caracteres
  • La salida de hash_hmac probablemente tendría más entropía que la contraseña misma
  • La contraseña está codificada por dos funciones diferentes

Contras

  • Solo se utilizan 64 caracteres para construir el hash de pez globo


Edición 1: esta pregunta aborda solo la integración PHP de blowfish / bcrypt. ¡Gracias por los comentarios!

Frederik Kammer
fuente
3
Blowfish no es el único que trunca la contraseña, lo que induce a error a la gente a pensar que es más seguro de lo que realmente es. Aquí hay una historia interesante del límite de 8 caracteres.
DOK
2
¿Es el truncamiento de 72 caracteres fundamental para el algoritmo Blowfish, o solo la implementación de PHP? IIRC Blowfish también se usa en (al menos algunos) 'nixes para cifrar las contraseñas de los usuarios.
Douglas B. Staple
3
El problema es con Bcrypt, no con Blowfish. Puedo reproducir este problema solo con Python y Bcrypt.
Blender
@Blender: Gracias por tu comentario y tu trabajo en él. No pude encontrar diferentes funciones en php para blowfish y bcrypt y aunque son las mismas. ¿Pero no hace ninguna diferencia para mí en php? Preferiría usar la función php estándar.
Frederik Kammer
1
Consulte también el marco de hash de contraseñas PHP de Openwall (PHPass). Es portátil y está reforzado contra varios ataques comunes a las contraseñas de los usuarios. El tipo que escribió el marco (SolarDesigner) es el mismo que escribió John The Ripper y se sienta como juez en el Concurso de hash de contraseñas . Así que sabe un par de cosas sobre ataques a contraseñas.
jww

Respuestas:

135

El problema aquí es básicamente un problema de entropía. Así que comencemos a buscar allí:

Entropía por carácter

El número de bits de entropía por byte es:

  • Personajes Hex
    • Bits: 4
    • Valores: 16
    • Entropía en 72 caracteres: 288 bits
  • Alfanumérico
    • Bits: 6
    • Valores: 62
    • Entropía en 72 caracteres: 432 bits
  • Símbolos "comunes"
    • Bits: 6.5
    • Valores: 94
    • Entropía en 72 caracteres: 468 bits
  • Bytes completos
    • Bits: 8
    • Valores: 255
    • Entropía en 72 caracteres: 576 bits

Entonces, cómo actuamos depende del tipo de personajes que esperamos.

El primer problema

El primer problema con su código es que su paso de hash "pimienta" está generando caracteres hexadecimales (ya que el cuarto parámetro hash_hmac()no está configurado).

Por lo tanto, al aplicar hash a su pimienta, está efectivamente reduciendo la entropía máxima disponible para la contraseña en un factor de 2 (de 576 a 288 bits posibles ).

El segundo problema

Sin embargo, sha256solo proporciona 256bits de entropía en primer lugar. De modo que está reduciendo efectivamente 576 bits a 256 bits. Su paso hash * inmediatamente *, por definición, pierde al menos el 50% de la posible entropía en la contraseña.

Podría resolver esto parcialmente cambiando a SHA512, donde solo reduciría la entropía disponible en aproximadamente un 12%. Pero esa sigue siendo una diferencia significativa. Ese 12% reduce el número de permutaciones en un factor de 1.8e19. Ese es un gran número ... Y ese es el factor que lo reduce por ...

El problema subyacente

El problema subyacente es que hay tres tipos de contraseñas de más de 72 caracteres. El impacto que este sistema de estilo tiene en ellos será muy diferente:

Nota: de aquí en adelante, supongo que estamos comparando con un sistema de pimienta que se usa SHA512con salida sin procesar (no hexadecimal).

  • Contraseñas aleatorias de alta entropía

    Estos son sus usuarios que utilizan generadores de contraseñas que generan claves grandes para contraseñas. Son aleatorios (generados, no elegidos por humanos) y tienen una alta entropía por carácter. Estos tipos utilizan bytes altos (caracteres> 127) y algunos caracteres de control.

    Para este grupo, su función hash reducirá significativamente su entropía disponible en bcrypt.

    Déjame decirlo de nuevo. Para los usuarios que utilizan contraseñas largas de alta entropía, su solución reduce significativamente la seguridad de su contraseña en una cantidad mensurable. (62 bits de entropía perdidos para una contraseña de 72 caracteres y más para contraseñas más largas)

  • Contraseñas aleatorias de entropía media

    Este grupo utiliza contraseñas que contienen símbolos comunes, pero no bytes altos ni caracteres de control. Estas son sus contraseñas que se pueden escribir.

    Para este grupo, va a desbloquear un poco más entropía (no crearla, pero permitir que más entropía quepa en la contraseña de bcrypt). Cuando digo un poco, me refiero a un poco. El punto de equilibrio se produce cuando maximiza los 512 bits que tiene SHA512. Por lo tanto, el pico es de 78 caracteres.

    Déjame decirlo de nuevo. Para esta clase de contraseñas, solo puede almacenar 6 caracteres adicionales antes de quedarse sin entropía.

  • Contraseñas no aleatorias de baja entropía

    Este es el grupo que utiliza caracteres alfanuméricos que probablemente no se generen al azar. Algo como una cita bíblica o algo así. Estas frases tienen aproximadamente 2,3 bits de entropía por carácter.

    Para este grupo, puede desbloquear significativamente más entropía (no crearla, pero permitir que más se ajuste a la entrada de contraseña de bcrypt) mediante hash. El punto de equilibrio es de alrededor de 223 caracteres antes de que te quedes sin entropía.

    Digámoslo de nuevo. Para esta clase de contraseñas, el pre-hash definitivamente aumenta la seguridad de manera significativa.

De vuelta al mundo real

Este tipo de cálculos de entropía realmente no importan mucho en el mundo real. Lo que importa es adivinar la entropía. Eso es lo que afecta directamente lo que pueden hacer los atacantes. Eso es lo que quieres maximizar.

Si bien se ha realizado poca investigación para adivinar la entropía, hay algunos puntos que me gustaría señalar.

Las posibilidades de adivinar al azar 72 caracteres correctos seguidos son extremadamente bajas. Es más probable que ganes la lotería Powerball 21 veces, que tener esta colisión ... Así de grande es el número del que estamos hablando.

Pero es posible que no lo encontremos estadísticamente. En el caso de las frases, la probabilidad de que los primeros 72 caracteres sean iguales es mucho mayor que para una contraseña aleatoria. Pero sigue siendo trivialmente bajo (es más probable que ganes la lotería Powerball 5 veces, según 2.3 bits por carácter).

Prácticamente

Prácticamente, realmente no importa. Las posibilidades de que alguien adivine correctamente los primeros 72 caracteres, donde los últimos marcan una diferencia significativa, son tan bajas que no vale la pena preocuparse. ¿Por qué?

Bueno, digamos que estás tomando una frase. Si la persona puede acertar con los primeros 72 caracteres, es muy afortunado (no es probable) o es una frase común. Si es una frase común, la única variable es cuánto tiempo debe hacerse.

Pongamos un ejemplo. Tomemos una cita de la Biblia (solo porque es una fuente común de texto extenso, no por ninguna otra razón):

No codiciarás la casa de tu prójimo. No codiciarás a la mujer de tu prójimo, ni a su siervo, ni a su sierva, ni a su buey, ni a su asno, ni nada que sea de tu prójimo.

Eso es 180 caracteres. El carácter 73 es el gdel segundo neighbor's. Si adivinó tanto, es probable que no se detenga en nei, sino que continúe con el resto del versículo (ya que así es como es probable que se use la contraseña). Por lo tanto, su "hash" no agregó mucho.

Por cierto: Absolutamente NO estoy abogando por el uso de una cita bíblica. De hecho, todo lo contrario.

Conclusión

Realmente no vas a ayudar mucho a las personas que usan contraseñas largas usando hash primero. Algunos grupos definitivamente pueden ayudar. Algunos definitivamente puedes lastimarlos.

Pero al final, nada de eso es demasiado significativo. Los números con los que estamos lidiando son MUY demasiado altos. La diferencia de entropía no será mucha.

Es mejor dejar bcrypt como está. Es más probable que arruines el hash (literalmente, ya lo has hecho y no eres el primero ni el último en cometer ese error) que el ataque que estás tratando de prevenir.

Concéntrese en asegurar el resto del sitio. Y agregue un medidor de entropía de contraseña al cuadro de contraseña al registrarse para indicar la fuerza de la contraseña (e indicar si una contraseña es demasiado larga y el usuario puede desear cambiarla) ...

Ese es mi $ 0.02 al menos (o posiblemente mucho más de $ 0.02) ...

En cuanto al uso de un pimiento "secreto":

Literalmente, no hay ninguna investigación sobre la introducción de una función hash en bcrypt. Por lo tanto, no está claro en el mejor de los casos si introducir un hash "salpicado" en bcrypt alguna vez causará vulnerabilidades desconocidas (sabemos que hacerlo hash1(hash2($value))puede exponer vulnerabilidades significativas en torno a la resistencia a colisiones y los ataques de preimagen).

Teniendo en cuenta que ya está considerando almacenar una clave secreta (el "pimiento"), ¿por qué no usarla de una manera bien estudiada y comprendida? ¿Por qué no cifrar el hash antes de almacenarlo?

Básicamente, después de aplicar el hash a la contraseña, introduzca toda la salida del hash en un algoritmo de cifrado sólido. Luego almacene el resultado encriptado.

Ahora, un ataque de inyección de SQL no filtrará nada útil, porque no tienen la clave de cifrado. Y si se filtra la clave, los atacantes no estarán mejor que si usas un hash simple (lo cual es demostrable, algo con el pimiento "pre-hash" no proporciona).

Nota: si elige hacer esto, use una biblioteca. Para PHP, recomiendo encarecidamente el Zend\Cryptpaquete Zend Framework 2 . De hecho, es el único que recomendaría en este momento. Ha sido revisado enérgicamente y toma todas las decisiones por usted (lo cual es algo muy bueno) ...

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);
}

Y es beneficioso porque está utilizando todos los algoritmos de formas que se comprenden y se estudian bien (al menos relativamente). Recuerda:

Cualquiera, desde el aficionado más despistado hasta el mejor criptógrafo, puede crear un algoritmo que él mismo no pueda romper.

ircmaxell
fuente
6
Muchas, muchas gracias por esta detallada respuesta. ¡Esto realmente me ayuda!
Frederik Kammer
1
Mi cumplido por esta respuesta. Sin embargo, un pequeño detalle, es la gran mayoría de los usuarios, que usan contraseñas, palabras y derivados muy débiles contenidos en un diccionario para descifrar contraseñas, un pimiento los protegería independientemente de las preguntas de entrofia. Para evitar perder entrofia, puede simplemente concatenar contraseña y pimienta. Sin embargo, su sugerencia sobre cifrar el valor hash es probablemente la mejor solución para agregar un secreto del lado del servidor.
martinstoeckli
2
@martinstoeckli: Mi problema con el concepto de pimienta no está en su valor. Es en que la aplicación de la "pimienta" entra en territorio desconocido en términos de algoritmos criptográficos. Eso no es nada bueno. En cambio, creo que las primitivas criptográficas deben combinarse de una manera que estén diseñadas para ir juntas. Básicamente, el concepto central de un pimiento me suena a oídos como si algunas personas que no sabían nada sobre criptografía dijeran "¡Más hashes son mejores! ¡Tenemos sal, la pimienta también es buena!" . Prefiero tener una
implícita
@ircmaxell - Sí, conozco su punto de vista y estoy de acuerdo, siempre que los valores hash se cifren posteriormente. Si no da este paso adicional, un ataque de diccionario simplemente revelará demasiadas contraseñas débiles, incluso con un buen algoritmo hash.
martinstoeckli
@martinstoeckli: No estoy de acuerdo con eso. El almacenamiento de secretos no es algo trivial. En cambio, si usa bcrypt con un buen costo (12 en la actualidad), todas las contraseñas excepto la más débil son seguras (el diccionario y las contraseñas triviales son las débiles). Así que prefiero recomendar que las personas se centren en educar al usuario con medidores de fuerza y lograr que usen mejores contraseñas en primer lugar ...
ircmaxell
5

Sin duda, modificar las contraseñas es algo bueno, pero veamos por qué.

Primero debemos responder a la pregunta de cuándo ayuda exactamente un pimiento. El pimiento solo protege las contraseñas, siempre que se mantenga en secreto, por lo que si un atacante tiene acceso al servidor, no sirve de nada. Sin embargo, un ataque mucho más fácil es la inyección SQL, que permite el acceso de lectura a la base de datos (a nuestros valores hash), preparé una demostración de inyección SQL para mostrar lo fácil que puede ser (haga clic en la flecha siguiente para obtener un entrada).

Entonces, ¿qué ayuda realmente la pimienta? Mientras el pimiento permanezca en secreto, protege las contraseñas débiles de un ataque de diccionario. La contraseña 1234se convertiría entonces en algo así como 1234-p*deDIUZeRweretWy+.O. Esta contraseña no solo es mucho más larga, también contiene caracteres especiales y nunca formará parte de ningún diccionario.

Ahora podemos estimar qué contraseñas usarán nuestros usuarios, probablemente más usuarios ingresarán contraseñas débiles, ya que hay usuarios con contraseñas entre 64-72 caracteres (en realidad esto será muy raro).

Otro punto es el rango de la fuerza bruta. La función hash sha256 devolverá una salida de 256 bits o combinaciones de 1.2E77, eso es demasiado para la fuerza bruta, incluso para las GPU (si calculé correctamente, esto necesitaría aproximadamente 2E61 años en una GPU en 2013). Por lo que no obtenemos una verdadera desventaja aplicando el pimiento. Debido a que los valores hash no son sistemáticos, no puede acelerar la fuerza bruta con patrones comunes.

PD: Hasta donde yo sé, el límite de 72 caracteres es específico del algoritmo de BCrypt. La mejor respuesta que encontré es esta .

PPS Creo que su ejemplo es defectuoso, no puede generar el hash con la longitud completa de la contraseña y verificarlo con una truncada. Probablemente quisiste aplicar el pimiento de la misma manera para generar el picadillo y para verificarlo.

martinstoeckli
fuente
Con respecto a su PPS, solo puedo decir: Sí, puede verificar la contraseña truncada con el hash de la no truncada y aún así obtener true. De eso se trata esta pregunta. Échale
Sliq
@Panique: el problema no es el cálculo del hash de BCrypt, es el HMAC anterior. Para generar el hash SHA, el OP usa la contraseña completa y usa el resultado como entrada para BCrypt. Para la verificación, trunca la contraseña antes de calcular el hash SHA, luego usa este resultado completamente diferente como entrada para BCrypt. El HMAC acepta entradas de cualquier longitud.
martinstoeckli
2

Bcrypt utiliza un algoritmo basado en el costoso algoritmo de configuración de claves Blowfish.

El límite de contraseña recomendado de 56 bytes (incluido el byte de terminación nulo) para bcrypt se relaciona con el límite de 448 bits de la clave Blowfish. Los bytes que superen ese límite no se mezclan completamente en el hash resultante. El límite absoluto de 72 bytes en las contraseñas bcrypt es, por lo tanto, menos relevante, si se considera el efecto real de esos bytes en el hash resultante.

Si cree que sus usuarios normalmente elegirían contraseñas de más de 55 bytes de longitud, recuerde que siempre puede aumentar las rondas de estiramiento de contraseñas, para aumentar la seguridad en el caso de una violación de la tabla de contraseñas (aunque esto tiene que ser mucho en comparación con agregar más caracteres). Si los derechos de acceso de los usuarios son tan críticos que los usuarios normalmente requerirían una contraseña enormemente larga, entonces la caducidad de la contraseña también debería ser corta, como 2 semanas. Esto significa que es mucho menos probable que una contraseña siga siendo válida mientras un pirata informático invierte sus recursos en vencer el factor de trabajo involucrado en probar cada contraseña de prueba para ver si produce un hash coincidente.

Por supuesto, en el caso de que la tabla de contraseñas no sea violada, solo deberíamos permitir a los piratas informáticos, como máximo, diez intentos de adivinar la contraseña de 55 bytes de un usuario, antes de bloquear la cuenta del usuario;)

Si decide realizar un hash previo de una contraseña de más de 55 bytes, entonces debe usar SHA-384, ya que tiene la salida más grande sin sobrepasar el límite.

Phil
fuente
1
"la caducidad de la contraseña también debe ser corta, como 2 semanas" de "contraseñas masivamente largas", de verdad, ¿por qué molestarse en guardar la contraseña entonces, simplemente use el restablecimiento de contraseña cada vez? En serio, esa es la solución incorrecta, cambie a la autenticación de dos factores con un token.
zaph
Gracias @zaph. ¿Puedes señalarme un ejemplo de eso? Suena interesante.
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 secretos memorizados : Los verificadores NO DEBEN requerir que los secretos memorizados se cambien arbitrariamente (por ejemplo, periódicamente) . Consulte también Hacia mejores requisitos de contraseña de Jim Fenton.
zaph
1
La cuestión es que cuanto más a menudo se requiere que un usuario cambie una contraseña, peores se vuelven las opciones de contraseña, lo que reduce la seguridad. El usuario tiene una cantidad limitada de buenas contraseñas memorizables y que se agoten, o bien elegir realmente malas contraseñas o escribirlos en notas Post-it pegado a la parte inferior del teclado, etc.
Zaph