bcrypt es una función de hash de contraseñas diseñada por Niels Provos y David Mazières, basada en el cifrado Blowfish y presentada en USENIX en 1999. [1] Además de incorporar una sal para proteger contra ataques de tabla arco iris , bcrypt es una función adaptativa: con el tiempo, el recuento de iteraciones se puede aumentar para hacerlo más lento, por lo que sigue siendo resistente a ataques de búsqueda de fuerza bruta incluso con un aumento en el poder de cálculo.
La función bcrypt es el algoritmo de hash de contraseña predeterminado para OpenBSD , [2] [ se necesita una fuente no primaria ] y era el predeterminado para algunas distribuciones de Linux como SUSE Linux . [3]
Hay implementaciones de bcrypt en C , C++ , C# , Embarcadero Delphi , Elixir , [4] Go , [5] Java , [6] [7] JavaScript , [8] Perl , PHP , Ruby , Python y otros lenguajes.
Blowfish es conocido entre los cifrados de bloques por su costosa fase de configuración de claves. Comienza con subclaves en un estado estándar, luego utiliza este estado para realizar un cifrado de bloque utilizando parte de la clave y utiliza el resultado de ese cifrado (que es más preciso en el hash) para reemplazar algunas de las subclaves. Luego utiliza este estado modificado para cifrar otra parte de la clave y utiliza el resultado para reemplazar más subclaves. Procede de esta manera, utilizando un estado modificado progresivamente para hacer un hash de la clave y reemplazar fragmentos de estado, hasta que se hayan establecido todas las subclaves.
Provos y Mazières aprovecharon esta situación y la llevaron más lejos. Desarrollaron un nuevo algoritmo de configuración de claves para Blowfish, y llamaron al cifrado resultante "Eksblowfish" ("Blowfish, programa de claves costoso"). La configuración de claves comienza con una forma modificada de la configuración de claves estándar de Blowfish, en la que se utilizan tanto la sal como la contraseña para configurar todas las subclaves. A continuación, hay una serie de rondas en las que se aplica el algoritmo de codificación estándar de Blowfish, utilizando alternativamente la sal y la contraseña como clave, y cada ronda comienza con el estado de la subclave de la ronda anterior. En teoría, esto no es más fuerte que el programa de claves estándar de Blowfish, pero el número de rondas de cambio de clave es configurable; por lo tanto, este proceso se puede hacer arbitrariamente lento, lo que ayuda a disuadir los ataques de fuerza bruta al hash o la sal.
La entrada de la función bcrypt es la cadena de contraseña (hasta 72 bytes), un costo numérico y un valor de sal de 16 bytes (128 bits). La sal suele ser un valor aleatorio. La función bcrypt utiliza estas entradas para calcular un hash de 24 bytes (192 bits). La salida final de la función bcrypt es una cadena con el formato:
$2<a/b/x/y>$[costo]$[sal de 22 caracteres][hash de 31 caracteres]
Por ejemplo, con la contraseña de entrada abc123xyz
, el costo 12
y una sal aleatoria, la salida de bcrypt es la cadena
$2a$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW\__/\/ \____________________/\_____________________________/Costo de Alg Salt Hash
Dónde:
$2a$
:El identificador del algoritmo hash (bcrypt)12
:Costo de entrada (2 12 es decir 4096 rondas)R9h/cIPz0gi.URNNX3kh2O
:Una codificación base-64 de la sal de entradaPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
:Una codificación base 64 de los primeros 23 bytes del hash de 24 bytes calculadoLa codificación base-64 en bcrypt utiliza la tabla ./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
[ 9], que difiere de la codificación Base64 RFC 4648 .
2$ (1999)
La especificación original de bcrypt definía un prefijo $2$
. Esto sigue el formato Modular Crypt Format [10] utilizado al almacenar contraseñas en el archivo de contraseñas de OpenBSD:
$1$
:Cifrado basado en MD5 ('md5crypt')$2$
:Cripto basado en Blowfish ('bcrypt')$sha1$
:Cifrado basado en SHA-1 ('sha1crypt')$5$
:Cifrado basado en SHA-256 ('sha256crypt')$6$
:Cifrado basado en SHA-512 ('sha512crypt')$2a$
La especificación original no definía cómo manejar caracteres no ASCII ni cómo manejar un terminador nulo. La especificación fue revisada para especificar que al aplicar hashes a cadenas:
Con este cambio la versión pasó a ser $2a$
[11]
$2x$, $2y$ (junio de 2011)
En junio de 2011, se descubrió un error en crypt_blowfish , una implementación PHP de bcrypt. Manejaba incorrectamente los caracteres con el 8.º bit establecido. [12] Sugirieron que los administradores de sistemas actualizaran su base de datos de contraseñas existente, reemplazando $2a$
con $2x$
, para indicar que esos hashes son incorrectos (y necesitan usar el antiguo algoritmo roto). También sugirieron la idea de que crypt_blowfish emita $2y$
para los hashes generados por el algoritmo corregido.
Nadie más, ni siquiera Canonical ni OpenBSD, adoptó la idea de 2x/2y. Este cambio de marcador de versión se limitó a crypt_blowfish .
2 mil millones de dólares (febrero de 2014)
Se descubrió un error en la implementación de bcrypt en OpenBSD. Utilizaba un valor de 8 bits sin signo para almacenar la longitud de la contraseña. [11] [13] [14] Para contraseñas de más de 255 bytes, en lugar de truncarse en 72 bytes, la contraseña se truncaría en el menor de 72 o la longitud módulo 256. Por ejemplo, una contraseña de 260 bytes se truncaría en 4 bytes en lugar de 72 bytes.
bcrypt fue creado para OpenBSD. Cuando encontraron un error en su biblioteca, decidieron aumentar el número de versión.
La función bcrypt que se muestra a continuación cifra el texto "OrpheanBeholderScryDoubt" 64 veces utilizando Blowfish . En bcrypt, la función de configuración de clave habitual de Blowfish se reemplaza por una función de configuración de clave costosa (EksBlowfishSetup):
Función bcrypt Entrada: cost: Número (4..31) log 2 (Iteraciones). p.ej. 12 ==> 2 12 = 4,096 iteraciones salt: matriz de bytes (16 bytes) sal aleatoria password: matriz de bytes (1..72 bytes) contraseña codificada en UTF-8 Salida: hash: matriz de bytes (24 bytes) //Inicializa el estado de Blowfish con un algoritmo de configuración de clave costoso //P: matriz de 18 subclaves (UInt32[18]) //S: Cuatro cuadros de sustitución (S-boxes), S 0 ... S 3 . Cada S-box tiene 1024 bytes (UInt32[256]) P , S ← EksBlowfishSetup( password , salt , cost ) //Cifrar repetidamente el texto "OrpheanBeholderScryDoubt" 64 veces ctext ← "OrpheanBeholderScryDoubt" //24 bytes ==> tres bloques de 64 bits repeat (64) ctext ← EncryptECB( P , S , ctext ) //cifrar usando Blowfish estándar en modo ECB // ctext de 24 bytes es el hash de contraseña resultante que devuelve Concatenate( cost , salt , ctext )
El algoritmo bcrypt depende en gran medida de su algoritmo de configuración de clave "Eksblowfish", que se ejecuta de la siguiente manera:
Función EksBlowfishSetup Entrada: contraseña: matriz de bytes (1..72 bytes) contraseña codificada en UTF-8 sal: matriz de bytes (16 bytes) sal aleatoria costo: Número (4..31) log 2 (Iteraciones). p.ej. 12 ==> 2 12 = 4,096 iteraciones Salida: P: matriz de UInt32 matriz de 18 subclaves por ronda S 1 ..S 4 : matriz de UInt32 matriz de cuatro SBoxes; cada SBox es 256 UInt32 ( es decir, cada SBox es 1 KiB) //Inicializar P (subclaves) y S (cuadros de sustitución) con los dígitos hexadecimales de pi P , S ← InitialState() //Permutar P y S en función de la contraseña y la sal P , S ← ExpandKey( P , S , contraseña , sal ) //Esta es la parte "Costosa" de la "Configuración de claves costosas". //De lo contrario, la configuración de claves es idéntica a Blowfish. repeat (2 cost ) P , S ← ExpandKey( P , S , password, 0) P , S ← ExpandKey( P , S , salt, 0) devuelve P , S
InitialState funciona como en el algoritmo Blowfish original, rellenando las entradas de la matriz P y del cuadro S con la parte fraccionaria en hexadecimal.
La función ExpandKey hace lo siguiente:
Función ExpandKey Entrada: P: matriz de UInt32 Matriz de 18 subclaves S 1 ..S 4 : UInt32[1024] Cuatro SBoxes de 1 KB contraseña: matriz de Bytes (1..72 bytes) contraseña codificada en UTF-8 sal: Byte[16] sal aleatoria Salida: P: matriz de UInt32 Matriz de 18 subclaves por ronda S 1 ..S 4 : UInt32[1024] Cuatro SBoxes de 1 KB //Mezcle la contraseña en la matriz de subclaves P para n ← 1 a 18, haga P n ← P n xor contraseña [32(n-1)..32n-1] //trate la contraseña como cíclica //Trate la sal de 128 bits como dos mitades de 64 bits (el tamaño del bloque Blowfish). saltHalf[0] ← salt [0..63] //64 bits inferiores de sal saltHalf[1] ← salt [64..127] //64 bits superiores de sal //Inicializa un búfer de 8 bytes (64 bits) con todos ceros. bloque ← 0 //Mezclar el estado interno en cajas P para n ← 1 a 9 //xor bloque de 64 bits con un bloque de medio bloque de sal de 64 bits ← xor saltHalf [(n-1) mod 2] // cada iteración alterna entre saltHalf [0] y saltHalf [1] // cifrar bloque usando la programación de claves actual bloque ← Cifrar( P , S , bloque ) P 2n ← bloque [0..31] //32 bits inferiores del bloque P 2n+1 ← bloque [32..63] //32 bits superiores del bloque //Mezclar el estado cifrado en las cajas S internas del estado para i ← 1 a 4 hacer para n ← 0 a 127 hacer bloque ← Encrypt( estado , bloque xor saltHalf [(n-1) mod 2]) //como arriba S i [2n] ← bloque [0..31] //32 bits inferiores S i [2n+1] ← bloque [32..63] //32 bits superiores devolver estado
Por lo tanto, es lo mismo que la programación de claves Blowfish normal, ya que todos los XOR con el valor de sal todo cero son ineficaces. es similar, pero utiliza la sal como una clave de 128 bits.ExpandKey(state, 0, key)
ExpandKey(state, 0, salt)
Muchas implementaciones de bcrypt truncan la contraseña a los primeros 72 bytes, siguiendo la implementación de OpenBSD.
El algoritmo matemático en sí mismo requiere inicialización con 18 subclaves de 32 bits (equivalentes a 72 octetos/bytes). La especificación original de bcrypt no exige ningún método en particular para mapear contraseñas basadas en texto del espacio de usuario a valores numéricos para el algoritmo. Un breve comentario en el texto menciona, pero no exige, la posibilidad de simplemente usar el valor codificado ASCII de una cadena de caracteres: "Finalmente, el argumento clave es una clave de cifrado secreta, que puede ser una contraseña elegida por el usuario de hasta 56 bytes (incluido un byte cero final cuando la clave es una cadena ASCII)". [1]
Tenga en cuenta que la cita anterior menciona contraseñas "de hasta 56 bytes", aunque el algoritmo en sí mismo utiliza un valor inicial de 72 bytes. Aunque Provos y Mazières no indican el motivo de la restricción más corta, es posible que hayan estado motivados por la siguiente declaración de la especificación original de Blowfish de Bruce Schneier : "El límite de 448 [bit] en el tamaño de la clave garantiza que [ sic ] cada bit de cada subclave dependa de cada bit de la clave". [15]
Las implementaciones han variado en su enfoque de convertir contraseñas en valores numéricos iniciales, incluyendo a veces la reducción de la fortaleza de las contraseñas que contienen caracteres no ASCII. [16]
Es importante tener en cuenta que bcrypt no es una función de derivación de claves (KDF) . Por ejemplo, bcrypt no se puede utilizar para derivar una clave de 512 bits a partir de una contraseña. Al mismo tiempo, algoritmos como pbkdf2 , scrypt y argon2 son funciones de derivación de claves basadas en contraseñas, donde el resultado se utiliza para el propósito de generar hashes de contraseñas en lugar de solo derivar claves.
Por lo general, el hash de contraseñas debe completarse en menos de 1000 ms. En este escenario, bcrypt es más seguro que pbkdf2, scrypt y argon2.
bcrypt tiene una longitud máxima de contraseña de 72 bytes. Este máximo proviene de la primera operación de la función ExpandKeyxor's
que contiene las 18 subclaves de 4 bytes (P) con la contraseña:
P 1 ..P 18 ← P 1 ..P 18 xor contraseñaBytes
La contraseña (codificada en UTF-8) se repite hasta que alcanza los 72 bytes. Por ejemplo, una contraseña de:
correct horse battery staple␀
(29 bytes)Se repite hasta que coincida con los 72 bytes de las subclaves de 18 P por ronda:
correct horse battery staple␀correct horse battery staple␀correct horse
(72 bytes)En el peor de los casos, una contraseña está limitada a 18 caracteres, cuando cada carácter requiere 4 bytes de codificación UTF-8. Por ejemplo:
𐑜𐑝𐑟𐑥𐑷𐑻𐑽𐑾𐑿𐑿𐑰𐑩𐑛𐑙𐑘𐑙𐑒𐑔
(18 caracteres, 72 bytes)El algoritmo bcrypt implica cifrar repetidamente el texto de 24 bytes:
OrpheanBeholderScryDoubt
(24 bytes)Esto genera 24 bytes de texto cifrado, por ejemplo:
85 20 af 9f 03 3d b3 8c 08 5f d2 5e 2d aa 5e 84 a2 b9 61 d2 f1 29 c9 a4
(24 bytes)La implementación canónica de OpenBSD trunca esto a 23 bytes [ cita requerida ] :
85 20 af 9f 03 3d b3 8c 08 5f d2 5e 2d aa 5e 84 a2 b9 61 d2 f1 29 c9
(23 bytes)No está claro por qué la implementación canónica elimina 8 bits del hash de contraseña resultante. [ cita requerida ]
Estos 23 bytes se convierten en 31 caracteres cuando se codifican en radix-64:
fQAtluK7q2uGV7HcJYncfII3WbJvIai
(31 caracteres)La codificación utilizada por la implementación canónica de OpenBSD utiliza el mismo alfabeto Base64 que crypt , que es ./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
. [9] Esto significa que la codificación no es compatible con el RFC 4648 más común . [ cita requerida ]
Cambio mínimo en la implementación de bcrypt para no requerir variables globales estáticas
La implementación de crypt() de SUSE admite la función de hash de contraseña Blowfish (id $2a) y los inicios de sesión del sistema de forma predeterminada también utilizan este método.