La Web Crypto API proporciona operaciones criptográficas primitivas directamente en el navegador y en Node.js (vía el objeto crypto global desde la versión 19, o require('crypto').webcrypto antes). No requiere librerías de terceros, usa aceleración hardware cuando está disponible y es la base segura para implementar autenticación, cifrado de datos del cliente y firmas digitales en aplicaciones web.
Hashes SHA-256 con crypto.subtle.digest
crypto.subtle.digest calcula el hash de un mensaje. El resultado es un ArrayBuffer que normalmente se convierte a hexadecimal para mostrarlo o compararlo:
async function sha256(mensaje) {
const encoder = new TextEncoder();
const datos = encoder.encode(mensaje);
const hashBuffer = await crypto.subtle.digest('SHA-256', datos);
// Convertir ArrayBuffer a hex string
return Array.from(new Uint8Array(hashBuffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
const hash = await sha256('Hola, mundo');
console.log(hash);
// "872e4e50ce9990d8b041330c47c9ddd11bec6b503ae9386a99da8584e9bb12c4"
// Para checksums de ficheros
async function hashFichero(file) {
const buffer = await file.arrayBuffer();
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
return Array.from(new Uint8Array(hashBuffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
Cifrado simétrico con AES-GCM
AES-GCM (Galois/Counter Mode) es el algoritmo de cifrado simétrico recomendado: autentica e integridad del mensaje además de cifrarlo, detectando cualquier modificación del texto cifrado. Cada cifrado necesita un vector de inicialización (IV) único y aleatorio:
async function generarClave() {
return crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true, // extractable: se puede exportar
['encrypt', 'decrypt']
);
}
async function cifrar(clave, texto) {
const iv = crypto.getRandomValues(new Uint8Array(12)); // IV de 12 bytes para GCM
const datos = new TextEncoder().encode(texto);
const cifrado = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
clave,
datos
);
// Guardar IV junto al texto cifrado (el IV no es secreto)
return { iv, cifrado };
}
async function descifrar(clave, iv, cifrado) {
const datos = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
clave,
cifrado
);
return new TextDecoder().decode(datos);
}
// Uso completo
const clave = await generarClave();
const { iv, cifrado } = await cifrar(clave, 'Mensaje secreto');
const texto = await descifrar(clave, iv, cifrado);
console.log(texto); // "Mensaje secreto"
Firma digital con ECDSA
ECDSA (Elliptic Curve Digital Signature Algorithm) con la curva P-256 permite firmar datos y verificar la firma, sin revelar la clave privada. Útil para tokens de autenticación del lado cliente:
async function generarParClaves() {
return crypto.subtle.generateKeyPair(
{ name: 'ECDSA', namedCurve: 'P-256' },
true,
['sign', 'verify']
);
}
async function firmar(clavePrivada, mensaje) {
const datos = new TextEncoder().encode(mensaje);
const firma = await crypto.subtle.sign(
{ name: 'ECDSA', hash: 'SHA-256' },
clavePrivada,
datos
);
return firma;
}
async function verificar(clavePublica, firma, mensaje) {
const datos = new TextEncoder().encode(mensaje);
return crypto.subtle.verify(
{ name: 'ECDSA', hash: 'SHA-256' },
clavePublica,
firma,
datos
);
}
const { privateKey, publicKey } = await generarParClaves();
const firma = await firmar(privateKey, 'Documento importante');
const valida = await verificar(publicKey, firma, 'Documento importante');
console.log('Firma válida:', valida); // true
Importar y exportar claves
exportKey e importKey permiten serializar claves para guardarlas en localStorage o IndexedDB, o transmitirlas al servidor:
// Exportar clave pública en formato SPKI (para compartir)
const clavePublicaExportada = await crypto.subtle.exportKey('spki', publicKey);
const base64 = btoa(String.fromCharCode(...new Uint8Array(clavePublicaExportada)));
// Importar clave pública recibida del servidor
async function importarClavePublica(base64) {
const binario = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
return crypto.subtle.importKey(
'spki',
binario,
{ name: 'ECDSA', namedCurve: 'P-256' },
true,
['verify']
);
}
// Exportar clave simétrica como JSON Web Key (JWK)
const claveJwk = await crypto.subtle.exportKey('jwk', clave);
// { kty: 'oct', k: '...', alg: 'A256GCM', ext: true, key_ops: ['encrypt', 'decrypt'] }
// Importar de nuevo desde JWK
const claveImportada = await crypto.subtle.importKey(
'jwk',
claveJwk,
{ name: 'AES-GCM' },
true,
['encrypt', 'decrypt']
);
Para derivar una clave de cifrado a partir de una contraseña del usuario, usa PBKDF2 o scrypt (también disponibles en crypto.subtle) en lugar de usar la contraseña directamente como clave. Nunca almacenes la clave privada en localStorage: usa IndexedDB con el flag extractable: false para que la clave solo sea usable desde el navegador donde se generó.
