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.
Desafío
FácilAhora 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.
- En
passwords.js, cambiahashPassword(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
scryptgenera a partir de la contraseña y de esa sal; también se escribe en hexadecimal: 128 caracteres. El tercer argumento descryptAsynces la longitud de la clave en bytes. - Une ambos con dos puntos. Un valor guardado tiene este aspecto:
f2bba0669d9fa2d203afc7e61bf8f065:c3f7cae352e023a8d4766da9a316b5a2c844100efff22c4079f89a8ab8512ac91c33227481c3afa88c3ba0e88347f9e3e0c22dad9d55f19688a350f71be8dbd2
- No necesitas cambiar
server.js. SuPOST /signupya llama ahashPasswordy guarda lo que devuelve. También tieneGET /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;
}
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
1Contraseñas
Bytes y texto hexadecimalDerivar una clave de contraseñaUna sal nuevaNunca almacenes contraseñasComparar bytes de claveVerificar una contraseñaReglas de registroPractica por tu cuenta: Playground de Express