sha2 / bcrypt en Rust: guía completa con ejemplos prácticos

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

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 usar finalize() y formatear con {:x}, necesitas el trait Digest en el ámbito.
  • No usar update() para datos grandes: Para archivos grandes, lee en fragmentos con update() 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!

Recommendations