Argon2 es una función de derivación de claves que fue seleccionada como ganadora de la Competencia de Hashing de Contraseñas de 2015. [1] [2] Fue diseñada por Alex Biryukov , Daniel Dinu y Dmitry Khovratovich de la Universidad de Luxemburgo . [3] La implementación de referencia de Argon2 se publica bajo una licencia Creative Commons CC0 (es decir, dominio público ) o la Licencia Apache 2.0 , y proporciona tres versiones relacionadas:
Los tres modos permiten la especificación mediante tres parámetros que controlan:
Si bien no existe un criptoanálisis público aplicable a Argon2d, hay dos ataques publicados a la función Argon2i. El primer ataque es aplicable solo a la versión anterior de Argon2i, mientras que el segundo se ha extendido a la última versión (1.3). [5]
El primer ataque muestra que es posible calcular una función Argon2i de un solo paso utilizando entre un cuarto y un quinto del espacio deseado sin penalización de tiempo, y calcular una función Argon2i de múltiples pasos utilizando solo el espacio N / e (≈ N /2.72) sin penalización de tiempo. [6] Según los autores de Argon2, este vector de ataque se corrigió en la versión 1.3. [7]
El segundo ataque muestra que Argon2i puede calcularse mediante un algoritmo que tiene una complejidad O( n 7/4 log( n )) para todas las opciones de parámetros σ (costo de espacio), τ (costo de tiempo) y número de subprocesos tales que n = σ ∗ τ . [8] Los autores de Argon2 afirman que este ataque no es eficiente si Argon2i se utiliza con tres o más pasadas. [7] Sin embargo, Joël Alwen y Jeremiah Blocki mejoraron el ataque y demostraron que para que el ataque falle, Argon2i v1.3 necesita más de 10 pasadas sobre la memoria. [5]
Para abordar estas preocupaciones, RFC9106 recomienda utilizar Argon2id para mitigar en gran medida dichos ataques. [9]
Fuente: [4]
Función Argon2 Entradas: password ( P ): Bytes (0..2 32 -1) Contraseña (o mensaje) a ser hasheada salt ( S ): Bytes (8..2 32 -1) Sal (16 bytes recomendados para hashear contraseñas) parallelism ( p ): Número (1..2 24 -1) Grado de paralelismo (es decir, número de hilos) tagLength ( T ): Número (4..2 32 -1) Número deseado de bytes devueltos memorySizeKB ( m ): Número (8p..2 32 -1) Cantidad de memoria (en kibibytes ) a usar iterations ( t ): Número (1..2 32 -1) Número de iteraciones a realizar version ( v ): Número (0x13) La versión actual es 0x13 (19 decimal) key ( K ): Bytes (0..2 32 -1) Clave opcional (Errata: PDF dice 0..32 bytes, RFC dice 0..2 32 bytes) associateData ( X ): Bytes (0..2 32 -1) Datos adicionales arbitrarios opcionales hashType ( y ): Número (0=Argon2d, 1=Argon2i, 2=Argon2id) Salida: etiqueta: Bytes (tagLength) Los bytes generados resultantes, tagLength bytes de longitud Generar bloque inicial de 64 bytes H 0 . Todos los parámetros de entrada se concatenan y se introducen como fuente de entropía adicional. Erratas: RFC dice que H 0 es de 64 bits; PDF dice que H 0 es de 64 bytes. Erratas: RFC dice que el hash es H^, el PDF dice que es ℋ (pero no documenta qué es ℋ). En realidad es Blake2b. A los elementos de longitud variable se les antepone su longitud como números enteros little-endian de 32 bits. buffer ← paralelismo ∥ tagLength ∥ memorySizeKB ∥ iteraciones ∥ versión ∥ hashType ∥ Longitud(contraseña) ∥ Contraseña ∥ Longitud(sal) ∥ sal ∥ Longitud(clave) ∥ clave ∥ Longitud(datos asociados) ∥ datos asociados H 0 ← Blake2b(buffer, 64) //el tamaño de hash predeterminado de Blake2b es de 64 bytes Calcular el número de bloques de 1 KB redondeando memorySizeKB al múltiplo más cercano de 4*paralelismo kibibytes blockCount ← Piso(memoriaTamañoKB, 4*paralelismo) Asignar una matriz bidimensional de bloques de 1 KiB (paralelismo filas x columnCount columnas) columnCount ← blockCount / parallelism; //En el RFC, columnCount se conoce como q Calcular el primer y segundo bloque (es decir, columna cero y uno) de cada carril (es decir, fila) para i ← 0 a paralelismo-1 hacer para cada fila B i [0] ← Hash(H 0 ∥ 0 ∥ i, 1024) //Generar un resumen de 1024 bytes B i [1] ← Hash(H 0 ∥ 1 ∥ i, 1024) //Generar un resumen de 1024 bytes Calcular las columnas restantes de cada carril para i ← 0 a paralelismo-1 hacer //para cada fila para j ← 2 a columnCount-1 hacer //para cada columna subsiguiente //los índices i' y j' dependen de si es Argon2i, Argon2d o Argon2id (Ver sección 3.4) i′, j′ ← GetBlockIndexes(i, j) //la función GetBlockIndexes no está definida B i [j] = G(B i [j-1], B i′ [j′]) //la función hash G no está definida Pases posteriores cuando iteraciones > 1 para nIteration ← 2 a iteraciones hacer para i ← 0 a paralelismo-1 hacer para cada fila para j ← 0 a columnCount-1 hacer //para cada columna subsiguiente //Los índices i' y j' dependen de si es Argon2i, Argon2d o Argon2id (consulte la sección 3.4) i′, j′ ← ObtenerÍndicesDeBloque(i, j) si j == 0 entonces B i [0] = B i [0] xor G(B i [columnCount-1], B i′ [j′]) de lo contrario B i [j] = B i [j] xor G(B i [j-1], B i′ [j′]) Calcular el bloque final C como el XOR de la última columna de cada fila C ← B 0 [columnCount-1] para i ← 1 hasta paralelismo-1 hacer C ← C xor B i [columnCount-1] Calcular la etiqueta de salida que devuelve Hash(C, tagLength)
Argon2 utiliza una función hash capaz de generar resúmenes de hasta 232 bytes de longitud. Esta función hash está integrada internamente en Blake2 .
Función Hash(message, digestSize) Entradas: message: Bytes (0..2 32 -1) Mensaje a codificar digestSize: Entero (1..2 32 ) Número deseado de bytes a devolver Salida: digest: Bytes (digestSize) Los bytes generados resultantes, digestSize en bytes de longitud Hash es una función hash de longitud variable, creada con Blake2b, capaz de generar resume hasta 2 bytes de 32 bytes. Si el digestSize solicitado es de 64 bytes o menos, entonces usamos Blake2b directamente if (digestSize <= 64) then return Blake2b(digestSize ∥ message, digestSize) //concatena el digestSize little endian de 32 bits con los bytes del mensaje Para hashes deseados de más de 64 bytes (por ejemplo, 1024 bytes para bloques Argon2), Usamos Blake2b para generar el doble de bloques de 64 bytes necesarios, y luego solo use 32 bytes de cada bloque Calcular la cantidad de bloques completos (sabiendo que solo vamos a utilizar 32 bytes de cada uno) r ← Ceil(digestSize/32)-2; Generar r bloques completos. El bloque inicial se genera a partir del mensaje V 1 ← Blake2b(digestSize ∥ message, 64); los bloques subsiguientes se generan a partir de los bloques anteriores para i ← 2 hasta r, hacer V i ← Blake2b(V i-1 , 64) Generar el bloque final (posiblemente parcial) partialBytesNeeded ← tamañoDigesto – 32*r; V r+1 ← Blake2b(V r , bytes parciales necesarios) Concatenar los primeros 32 bytes de cada bloque V i (excepto el último bloque posiblemente parcial, que tomamos completo) Sea A i los 32 bytes inferiores del bloque V i y devuelva A 1 ∥ A 2 ∥ ... ∥ A r ∥ V r+1
A partir de mayo de 2023, la hoja de trucos de almacenamiento de contraseñas de OWASP recomienda que las personas "utilicen Argon2id con una configuración mínima de 19 MiB de memoria, un recuento de iteraciones de 2 y 1 grado de paralelismo". [10]
OWASP recomienda que se prefiera Argon2id sobre Argon2d y Argon2i porque proporciona una resistencia equilibrada tanto a los ataques basados en GPU como a los ataques de canal lateral. [10]
OWASP señala además que las siguientes opciones de Argon2id proporcionan una fuerza criptográfica equivalente y simplemente intercambian el uso de memoria por la carga de trabajo computacional: [10]