sha2 / bcrypt en Rust: guía completa con ejemplos prácticos
En el desarrollo moderno, la seguridad de los datos es un aspecto crítico de cualquier aplicación. Las librerías sha2 y bcrypt para el lenguaje Rust proporcionan herramientas potentes y seguras para el hashing criptográfico. sha2 implementa la familia de algoritmos SHA-2 (Secure Hash Algorithm 2), incluyendo SHA-224, SHA-256, SHA-384, SHA-512, así como sus variantes truncadas (SHA-512/224, SHA-512/256). bcrypt es una función hash criptográfica adaptativa, diseñada específicamente para almacenar contraseñas de forma segura. Utiliza una "sal" (salt) y un número configurable de rondas (cost factor), lo que la hace resistente a ataques con tablas rainbow y de fuerza bruta.
¿Para qué sirve esto? SHA-2 se usa para verificar la integridad de datos, crear firmas digitales, en sistemas de control de versiones (Git), blockchain y muchas otras áreas donde se necesita un hash rápido y confiable. Bcrypt, por su parte, es el estándar de facto para el hashing de contraseñas en aplicaciones web, bases de datos y sistemas de autenticación. Su principal ventaja es la "lentitud" configurable: puedes aumentar el número de rondas para compensar el crecimiento de la potencia computacional de los atacantes.
En este artículo, exploraremos ambas librerías en detalle: instalación, métodos principales, ejemplos de uso reales y errores comunes. Aprenderás a hashear datos correctamente, verificar contraseñas e integrar estas funciones en tus proyectos de Rust.
Instalación
Para usar sha2 y bcrypt en Rust, debes agregarlas al archivo Cargo.toml de tu proyecto. Abre la terminal en el directorio raíz del proyecto y ejecuta:
cargo add sha2 bcrypt
Este comando agregará las últimas versiones de las librerías en la sección [dependencies]. Alternativamente, puedes editar manualmente el Cargo.toml:
[dependencies]
sha2 = "0.10"
bcrypt = "0.15"
Después, ejecuta cargo build para descargar las dependencias y verificar que todo esté instalado correctamente.
Inicio rápido: ejemplo mínimo funcional
Comencemos con un ejemplo sencillo: hashear una cadena con SHA-256 y verificar una contraseña con bcrypt.
use sha2::{Sha256, Digest};
use bcrypt::{hash, verify, DEFAULT_COST};
fn main() {
// Hashing con SHA-256
let data = b"¡Hola, mundo!";
let mut hasher = Sha256::new();
hasher.update(data);
let result = hasher.finalize();
println!("Hash SHA-256: {:x}", result);
// Hashing de contraseña con Bcrypt
let password = "mi_contraseña_segura";
let hashed = hash(password, DEFAULT_COST).unwrap();
println!("Hash Bcrypt: {}", hashed);
// Verificación de contraseña
let is_valid = verify(password, &hashed).unwrap();
println!("Contraseña válida: {}", is_valid);
}
Este código muestra las capacidades básicas de ambas librerías. La salida incluirá un hash hexadecimal SHA-256 de 64 caracteres y una cadena larga de bcrypt (que comienza con $2b$).
Métodos y funciones principales
Veamos las funciones y métodos clave de sha2 y bcrypt. Para cada uno, incluimos la firma, descripción y un ejemplo funcional.
1. sha2::Sha256::new()
Firma: pub fn new() -> Sha256
Descripción: Crea una nueva instancia del hasher SHA-256. Es el punto de partida para hashear datos.
use sha2::{Sha256, Digest};
let mut hasher = Sha256::new();
hasher.update(b"datos de prueba");
let hash = hasher.finalize();
println!("{:x}", hash);
2. sha2::Sha256::update()
Firma: pub fn update(&mut self, data: impl AsRef<[u8]>)
Descripción: Alimenta el hasher con datos adicionales. Se puede llamar múltiples veces para procesar datos en fragmentos.
use sha2::{Sha256, Digest};
let mut hasher = Sha256::new();
hasher.update(b"primera parte");
hasher.update(b"segunda parte");
let hash = hasher.finalize();
println!("{:x}", hash);
3. sha2::Sha256::finalize()
Firma: pub fn finalize(self) -> GenericArray<u8, U32>
Descripción: Finaliza el proceso de hashing y devuelve el hash resultante como un arreglo de bytes. Consume el hasher.
use sha2::{Sha256, Digest};
let mut hasher = Sha256::new();
hasher.update(b"datos finales");
let hash = hasher.finalize();
// Convertir a hexadecimal
let hex_hash = format!("{:x}", hash);
println!("Hash en hex: {}", hex_hash);
4. bcrypt::hash()
Firma: pub fn hash<S: AsRef<[u8]>>(password: S, cost: u32) -> Result<String, BcryptError>
Descripción: Hashea una contraseña usando bcrypt con el costo especificado. Devuelve una cadena que contiene la sal y el hash.
use bcrypt::{hash, DEFAULT_COST};
let password = "mi_clave_secreta";
let hashed = hash(password, DEFAULT_COST).unwrap();
println!("Hash generado: {}", hashed);
5. bcrypt::verify()
Firma: pub fn verify<S: AsRef<[u8]>>(password: S, hashed: &str) -> Result<bool, BcryptError>
Descripción: Verifica si una contraseña coincide con un hash bcrypt existente. Devuelve true si coinciden.
use bcrypt::verify;
let password = "mi_clave_secreta";
let hashed = "$2b$12$..."; // Hash previamente generado
let is_valid = verify(password, hashed).unwrap();
println!("¿Coincide? {}", is_valid);
6. bcrypt::DEFAULT_COST
Firma: pub const DEFAULT_COST: u32 = 12
Descripción: Constante que define el costo por defecto (12 rondas). Es un buen equilibrio entre seguridad y rendimiento.
use bcrypt::{hash, DEFAULT_COST};
let hashed = hash("contraseña", DEFAULT_COST).unwrap();
println!("Hash con costo por defecto: {}", hashed);
Ejemplos prácticos
Ejemplo 1: Hashing de archivos con SHA-256
Este ejemplo calcula el hash SHA-256 de un archivo completo, útil para verificar integridad.
use sha2::{Sha256, Digest};
use std::fs::File;
use std::io::{BufReader, Read};
fn hash_file(path: &str) -> String {
let file = File::open(path).expect("No se pudo abrir el archivo");
let mut reader = BufReader::new(file);
let mut hasher = Sha256::new();
let mut buffer = [0; 8192];
loop {
let bytes_read = reader.read(&mut buffer).expect("Error de lectura");
if bytes_read == 0 {
break;
}
hasher.update(&buffer[..bytes_read]);
}
format!("{:x}", hasher.finalize())
}
fn main() {
let hash = hash_file("ejemplo.txt");
println!("SHA-256 del archivo: {}", hash);
}
Ejemplo 2: Registro e inicio de sesión con bcrypt
Simula el registro de un usuario y la verificación de su contraseña al iniciar sesión.
use bcrypt::{hash, verify, DEFAULT_COST};
struct Usuario {
nombre: String,
hash_contraseña: String,
}
fn registrar(nombre: &str, contraseña: &str) -> Usuario {
let hash_contraseña = hash(contraseña, DEFAULT_COST).unwrap();
Usuario {
nombre: nombre.to_string(),
hash_contraseña,
}
}
fn iniciar_sesion(usuario: &Usuario, contraseña: &str) -> bool {
verify(contraseña, &usuario.hash_contraseña).unwrap()
}
fn main() {
let usuario = registrar("usuario1", "pass123");
println!("Usuario registrado: {}", usuario.nombre);
let exito = iniciar_sesion(&usuario, "pass123");
println!("Inicio de sesión exitoso: {}", exito);
let fallo = iniciar_sesion(&usuario, "contraseña_incorrecta");
println!("Intento fallido: {}", fallo);
}
Errores comunes y solución
- Error de costo inválido: El costo debe estar entre 4 y 31. Usa
DEFAULT_COST(12) si no estás seguro. - Hash mal formado: Al verificar, asegúrate de que el hash comience con
$2b$,$2a$o$2y$. - Olvidar importar
Digest: Para usarfinalize()y formatear con{:x}, necesitas el traitDigesten el ámbito. - No usar
update()para datos grandes: Para archivos grandes, lee en fragmentos conupdate()en lugar de cargar todo en memoria.
Conclusión
Las librerías sha2 y bcrypt son herramientas esenciales para cualquier desarrollador Rust que trabaje con seguridad de datos. sha2 ofrece hashing rápido y confiable para integridad y firmas digitales, mientras que bcrypt proporciona protección robusta para contraseñas gracias a su diseño adaptativo. Con los ejemplos y métodos cubiertos en esta guía, estás listo para implementar hashing seguro en tus proyectos Rust.
¿Tienes alguna pregunta o sugerencia? ¡Déjala en los comentarios!