La biblioteca de criptografía Ring proporciona una implementación de PBKDF2, una función de derivación de clave estandarizada que se puede utilizar para hashear contraseñas de manera segura. Usando el módulo ring::pbkdf2
, podemos derivar un hash de contraseña y luego verificar si un hash coincide o no con una contraseña dada.
Introducción a PBKDF2
La Función de Derivación de Clave Basada en Contraseña 2 (PBKDF2) está especificada en la Sección 5.2 de la RFC 2898. PBKDF2 no es un algoritmo muy moderno, pero, todavía se considera seguro siempre que se utilice correctamente con parámetros cuidadosamente seleccionados. PBKDF2 aplica una función pseudoaleatoria (como un HMAC) a la contraseña de entrada y al valor de sal, y repite el proceso un número de veces para producir una clave derivada que, luego puede utilizarse como clave criptográfica en operaciones posteriores. El trabajo computacional añadido de las numerosas iteraciones hace que el descifrado de contraseñas sea muy difícil. PBKDF2 también es recomendado por NIST y tiene implementaciones que se conforman con FIPS-140.
El documento RFC define la función PBKDF2 de la siguiente manera:
PBKDF2 (P, S, c, dkLen) -> DK
Estos son los parámetros de la función PBKDF2:
Options: PRF función pseudoaleatoria subyacente
(hLen denota la longitud en octetos
de la salida de la función
pseudoaleatoria)
Input: P contraseña, una cadena de octetos
S sal, una cadena de octetos
c número de iteraciones, un número
entero positivo
dkLen longitud prevista en octetos de la
clave derivada, un entero positivo,
en la mayoría (2 ^ 32 - 1) * hLen
Output: DK clave derivada, una cadena
dkLen-octet
Directrices de Uso de PBKDF2
- Las contraseñas débiles pueden ser vulnerables a ataques de fuerza bruta, por lo que se debe requerir a los usuarios que establezcan contraseñas fuertes que sean largas y contengan suficiente entropía.
- El uso de una sal de baja entropía puede hacer que el hash (clave derivada) sea vulnerable a ataques de pre-cálculo utilizando tablas arco iris (rainbow tables). El parámetro de sal mejora la seguridad de PBKDF2 cambiando la salida del hash para cualquier contraseña de entrada dada. La sal debe ser impredecible y seleccionada para ser única por usuario y por base de datos (esta parte a veces se llama pimienta ‘pepper’).
- El uso de un número bajo de iteraciones también puede hacer que el hash sea vulnerable a ataques de fuerza bruta. Cuanto mayor sea el número de iteraciones seleccionadas, más tiempo tarda en ejecutarse la función PBKDF2 y, por lo tanto, requiere más trabajo para descifrar las contraseñas con éxito. El número de iteraciones que debes seleccionar es un objetivo móvil y aumenta con el tiempo a medida que aumenta el poder de la CPU. OWASP proporciona un buen recurso sobre los parámetros recomendados aquí. Actualmente, para PBKDF2-HMAC-SHA256, el número de iteraciones recomendado es 600000 y para PBKDF2-HMAC-SHA512 es 210000.
- La longitud de la clave de salida necesita ser suficientemente larga para prevenir ataques de fuerza bruta y, por lo tanto, se recomienda una longitud de clave mínima de al menos 128 bits. Generalmente, la longitud de salida debe establecerse igual a la longitud de la función del hash elegido.
- Cuando PBKDF2 se utiliza con un HMAC, puede ser necesario prehashear manualmente la contraseña en algunos casos para prevenir ciertas vulnerabilidades de denegación de servicio. Esto puede ocurrir cuando la contraseña de entrada es más larga que el tamaño de bloque de la función de hash. Vea aquí para más detalles.
Resumen de Tipos
El módulo ring::pbkdf2
contiene los siguientes tipos y funciones:
struct Algorithm - El tipo de algoritmo PBKDF2. Se soportan los algoritmos PBKDF2_HMAC_SHA1
, PBKDF2_HMAC_SHA256
, PBKDF2_HMAC_SHA384
y PBKDF2_HMAC_SHA512
.
fn derive - El algoritmo PBKDF2 utilizado para derivar un hash de contraseña a partir de una contraseña dada. Pasamos el algoritmo elegido, el número de iteraciones a utilizar, una sal y el secreto (contraseña). El valor de hash se almacena en el array de bytes dado, out
. El tamaño del array out
determina el tamaño del valor de hash devuelto.
pub fn derive(
algorithm: Algorithm,
iterations: NonZeroU32,
salt: &[u8],
secret: &[u8],
out: &mut [u8]
)
fn verify - Comprueba si un secreto dado (contraseña) coincide con un hash derivado previamente. Pasamos el algoritmo, el número de iteraciones, la sal, el secreto a comprobar y el hash que se derivó previamente utilizando la función pbkdf2::derive
.
pub fn verify(
algorithm: Algorithm,
iterations: NonZeroU32,
salt: &[u8],
secret: &[u8],
previously_derived: &[u8]
) -> Result<(), Unspecified>
Importaciones en Rust
Comencemos importando los tipos requeridos en nuestro proyecto.
use std::num::NonZeroU32;
use ring::digest::SHA256_OUTPUT_LEN;
use ring::digest::SHA512_OUTPUT_LEN;
use ring::pbkdf2;
use ring::pbkdf2::Algorithm;
use ring::pbkdf2::PBKDF2_HMAC_SHA1;
use ring::pbkdf2::PBKDF2_HMAC_SHA256;
use ring::pbkdf2::PBKDF2_HMAC_SHA384;
use ring::pbkdf2::PBKDF2_HMAC_SHA512
Preparar las Iteraciones, la Sal y el Secreto
En el código a continuación, declaramos los parámetros que se pasarán a la función pbkdf2::derive
. En este ejemplo, estamos utilizando el algoritmo PBKDF2_HMAC_SHA256
y, por lo tanto, el número de iteraciones se establece en 600000 según lo recomendado por OWASP. La variable de iteraciones es de tipo NonZeroU32
de la biblioteca estándar de Rust, lo que evita establecer un conteo de iteraciones de cero.
const PBKDF2_HMAC_SHA256_ITERATIONS: u32 = 600_000; // número recomendado por OWASP para PBKDF2 con SHA256
let iterations = NonZeroU32::new(PBKDF2_HMAC_SHA256_ITERATIONS).unwrap();
let salt = b"random salt"; // esto debe generarse aleatoriamente, utilizando algún componente específico del usuario y un componente específico de la base de datos
let secret = b"strong password"; // selecciona una contraseña segura
println!("Secret/password value: {}", hex::encode(secret)); // no imprimas esto en producción
Derivar el Hash de la Contraseña y Almacenarlo
Luego, llamamos a la función pbkdf2::derive
con nuestros parámetros elegidos y almacenamos el hash en un array de bytes. En este ejemplo, el tamaño del hash de salida será de la misma longitud que la salida de SHA256, pero podríamos hacerlo más largo si fuera necesario. La longitud se establece eligiendo el tamaño del array password_hash
.
let mut password_hash = [0u8; SHA256_OUTPUT_LEN]; // inicializa con ceros
pbkdf2::derive(PBKDF2_HMAC_SHA256, iterations, salt, secret, &mut password_hash);
println!("Password hash: {}", hex::encode(password_hash)); // no imprimas esto en producción
Verificar si una Contraseña Coincide con el Hash de la Contraseña
Usando los mismos parámetros de entrada que antes, podemos llamar a la función pbkdf2::verify
para comprobar si una contraseña dada coincide con el hash derivado anteriormente. Si la contraseña coincide con el hash, la función verify
devuelve un Result::Ok
que contiene un ()
, de lo contrario, devuelve un Result::Err
que contiene error::Unspecified
(definido en el módulo de error
de Ring).
pbkdf2::verify(PBKDF2_HMAC_SHA256, iterations, salt, secret, &password_hash).unwrap(); // caso exitoso
pbkdf2::verify(PBKDF2_HMAC_SHA256, iterations, salt, "wrong password".as_bytes(), &password_hash).unwrap(); // caso fallado
Código de Ejemplo Completo
Aquí está el código de ejemplo completo como referencia. El ejemplo del escenario 2, a continuación, utiliza PBKDF2_HMAC_SHA512
con el número recomendado de iteraciones de 210000 y utiliza un tamaño de hash de salida igual al tamaño de SHA512.
use std::num::NonZeroU32;
use ring::digest::SHA256_OUTPUT_LEN;
use ring::digest::SHA512_OUTPUT_LEN;
use ring::pbkdf2;
use ring::pbkdf2::Algorithm;
use ring::pbkdf2::PBKDF2_HMAC_SHA1;
use ring::pbkdf2::PBKDF2_HMAC_SHA256;
use ring::pbkdf2::PBKDF2_HMAC_SHA384;
use ring::pbkdf2::PBKDF2_HMAC_SHA512;
fn main() {
// escenario 1 - PBKDF2_HMAC_SHA256
const PBKDF2_HMAC_SHA256_ITERATIONS: u32 = 600_000; //número recomendado por OWASP para PBKDF2 con SHA256
// Preparar iteraciones, sal y secreto.
let iterations = NonZeroU32::new(PBKDF2_HMAC_SHA256_ITERATIONS).unwrap();
let salt = b"random salt"; // esto debe generarse aleatoriamente, utilizando algún componente específico del usuario y un componente específico de la base de datos
let secret = b"strong password"; // selecciona una contraseña segura
println!("Secret/password value: {}", hex::encode(secret)); // no imprimas esto en producción
// Derivar el hash de la contraseña y almacenar
let mut password_hash = [0u8; SHA256_OUTPUT_LEN]; // inicializa con ceros
pbkdf2::derive(PBKDF2_HMAC_SHA256, iterations, salt, secret, &mut password_hash);
println!("Password hash: {}", hex::encode(password_hash)); //no imprimas esto en producción
// Verificar si una contraseña coincide o no con el hash de la contraseña almacenada
pbkdf2::verify(PBKDF2_HMAC_SHA256, iterations, salt, secret, &password_hash).unwrap(); // caso exitoso
//pbkdf2::verify(PBKDF2_HMAC_SHA256, iterations, salt, "wrong password".as_bytes(), &password_hash).unwrap(); // caso fallado
// escenario 2 - PBKDF2_HMAC_SHA512
const PBKDF2_HMAC_SHA512_ITERATIONS: u32 = 210_000; // número recomendado por OWASP para PBKDF2 con SHA256
// Preparar iteraciones, sal y secreto
let iterations = NonZeroU32::new(PBKDF2_HMAC_SHA512_ITERATIONS).unwrap();
let salt = b"random salt"; // esto debe generarse aleatoriamente, utilizando algún componente específico del usuario y un componente específico de la base de datos
let secret = b"strong password"; // selecciona una contraseña segura
println!("Secret/password value: {}", hex::encode(secret)); // no imprimas esto en producción
// Derivar el hash de la contraseña y almacenar
let mut password_hash = [0u8; SHA512_OUTPUT_LEN]; // inicializar con ceros
pbkdf2::derive(PBKDF2_HMAC_SHA512, iterations, salt, secret, &mut password_hash);
println!("Password hash: {}", hex::encode(password_hash)); // no imprimas esto en producción
// Verificar si una contraseña coincide o no con el hash de la contraseña almacenada
pbkdf2::verify(PBKDF2_HMAC_SHA512, iterations, salt, secret, &password_hash).unwrap(); // caso exitoso
//pbkdf2::verify(PBKDF2_HMAC_SHA512, iterations, salt, "wrong password".as_bytes(), &password_hash).unwrap(); // caso fallado
}
Conclusión
En esta publicación, presentamos el algoritmo PBKDF2, explicamos cómo funciona y describimos cómo elegir correctamente los parámetros para la función PBKDF2. Luego, explicamos cómo usar el módulo Ring pbkdf2
para derivar un hash de contraseña utilizando la función pbkdf2::derive
y verificar que una contraseña coincide con un hash de contraseña utilizando la función pbkdf2::verify
. Los ejemplos, muestran dos escenarios utilizando los algoritmos PBKDF2 más comúnmente utilizados; PBKDF2_HMAC_SHA256
y PBKDF2_HMAC_SHA512
.
Este artículo es una traducción realizada por @bananlabs. Puedes encontrar el artículo original aquí
Discussion (0)