Menu
CoddyTech

Nunca almacenes contraseñas

Parte de la sección Autenticación y seguridad del Journey de Node.js de Coddy. Lección 4 de 24.

Las personas reutilizan las contraseñas, por lo que una contraseña filtrada en texto plano puede exponer cuentas en otros sitios. Guarda un hash de contraseña con sal: un valor derivado que verificas volviéndolo a calcular, en lugar de decodificarlo.

const salt = randomBytes(16).toString('hex');
const key = await scryptAsync(inviteCode, salt, 32);
const saved = salt + '.' + key.toString('hex');

Este ejemplo combina una sal y una clave derivada para un código de invitación. El delimitador permite recuperar la sal durante la verificación y derivar otra clave con los mismos parámetros. Tu tarea sobre contraseñas usa su propio delimitador y longitud de clave. Genera la sal para cada contraseña nueva, deriva la clave y luego guarda ambas. Los hashes con sal siguen requiriendo contraseñas seguras, porque un atacante puede probar distintas opciones.

challenge icon

Desafío

Fácil

Ahora mismo, el registro guarda cada contraseña exactamente como se escribió. Haz que guarde un hash con sal en su lugar, para que una filtración de la base de datos no revele las contraseñas.

  1. En passwords.js, cambia hashPassword(password) para que devuelva el texto que guarda el registro, con el formato sal, dos puntos, hash:
    • La sal consta de 16 bytes aleatorios, se genera de nuevo en cada llamada y se escribe en hexadecimal: 32 caracteres del 0 al 9 y de la a a la f.
    • El hash es una clave de 64 bytes que scrypt genera a partir de la contraseña y de esa sal; también se escribe en hexadecimal: 128 caracteres. El tercer argumento de scryptAsync es la longitud de la clave en bytes.
    • Une ambos con dos puntos. Un valor guardado tiene este aspecto: f2bba0669d9fa2d203afc7e61bf8f065:c3f7cae352e023a8d4766da9a316b5a2c844100efff22c4079f89a8ab8512ac91c33227481c3afa88c3ba0e88347f9e3e0c22dad9d55f19688a350f71be8dbd2
  2. No necesitas cambiar server.js. Su POST /signup ya llama a hashPassword y guarda lo que devuelve. También tiene GET /debug/users/:username, que muestra lo que se guardó para un usuario. Esa ruta solo existe para que puedas consultarlo; una aplicación real nunca la incluye.

Pruébalo: Pulsa «Ejecutar». Envía POST /signup con {"username": "ada", "password": "correct horse"}. Obtendrás el estado 201 y {"username": "ada"}. Envía el mismo cuerpo con "username": "bob". Ahora envía GET /debug/users/ada y después GET /debug/users/bob. Cada passwordHash tiene 32 caracteres, dos puntos y otros 128 caracteres. Los dos valores son diferentes, aunque ada y bob hayan elegido la misma contraseña.

Pruébalo tú mismo

import { randomBytes, scrypt } from 'node:crypto';
import { promisify } from 'node:util';

const scryptAsync = promisify(scrypt);

// Convierte una contraseña en el texto que almacena signup.
// Por ahora, conserva la contraseña tal como se escribió.
export async function hashPassword(password) {
    return password;
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Autenticación y seguridad

Practica por tu cuenta: Playground de Express