El modulo crypto
Es el modulo de criptografía de nodejs que básicamente depende de nuestra instalación de openSSL, con el cual podemos calcular hashes, encriptar y desencriptar cadenas. Existen varios métodos los cuales se pueden usar en diferentes contextos como por ejemplo al guardar las contraseñas de nuestros usuarios donde solo ellos la pueden conocer , al guardar datos delicados donde el sistema debe poder codificarlos y decodificarlos y al transportar la información por un medio donde el canal debe estar protegido.
HASHES
Un hash es una función que transforma una cadena a otra cadena de una longitud definida de manera unidireccional por medio de un algoritmo, esto significa que solo se puede codificar pero no decodificar y para verificar el dato debemos hacer el mismo procedimiento para hacer la comparación o utilizar la fuerza bruta. Hoy en día las implementaciones recomendados en seguridad son: Argon2id, PBKDF2 y Bcrypt.
Algunos ejemplos de algoritmos y/o implementaciones para crear hashes con nodejs son:
-
MD5
Ejemplo con el algoritmo md5, no se recomienda usarlo porque es fácil de decodificar por medio de fuerza bruta.
const crypto = require('crypto'); const hash = crypto.createHash('md5'); hash.update('mypasswd'); console.log(`MD5: ${hash.digest('hex')}`); -
SHA
Ejemplo con el algoritmo sha 256, existen varios tipos de sha, se considera medianamente seguro y es muy usado para comprobar la integridad de una archivo, esto significa verificar que no fue modificado o dañado. No se recomienda su uso hoy en día para cifrar una clave.
const crypto = require('crypto'); const hash = crypto.createHash('sha256'); hash.update('mypasswd'); console.log(`SHA256: ${hash.digest('hex')}`); -
HMAC
Hmac usa un algoritmo y una clave privada y los codifica juntos para verificar la integridad del mensaje y la autenticidad de este. En el ejemplo se uso el algoritmo sha 256 y una clave que debe ser privada.
const crypto = require('crypto'); const hash = crypto.createHmac("sha256", "password1"); hash.update("mypasswd") console.log(`HMAC: ${hash.digest("hex")}`); -
pbkdf2
Ejemplo con una de las implementaciones más seguras y recomendadas en la industria, debido a que es muy resistente a los ataques de fuerza bruta, gracias a que se cifra con una clave, un salt (cadena de caracteres muy larga) y esto se hace el numero de veces que las iteraciones proporcionadas lo digan.
const crypto = require('crypto'); // random salt const salt = crypto.randomBytes(256).toString('hex'); const hash = crypto.pbkdf2Sync('mypasswd', salt, 100000, 512, 'sha512'); console.log(`pbkdf2: ${hash.toString('hex')}`) -
Bcrypt
Bcript es una implementación del algoritmo blowfish, el cual sigue siendo el más usado debido a que es muy seguro y esta soportado en casi todos los lenguajes, a medida que la potencia de calculo aumenta solo se debe aumentar su saltRound que por defecto es 10.
const bcrypt = require('bcrypt'); const saltRounds = 10; function generate(string, saltRounds) { return new Promise((resolve, reject) => { bcrypt.genSalt(saltRounds).then((salt)=> { console.log(`salt: ${salt}`); bcrypt.hash(string, salt).then((hash) => { return resolve(hash); }) .catch((err) => { return reject(err);; }); }) .catch((err) => { return reject(err); }); }); } function compare(string, hash) { return new Promise((resolve, reject) => { bcrypt.compare(string, hash) .then((valid)=>{ return resolve(valid); }) .catch((err) => { return reject(err); }); }) } const password = 'passwd example'; generate(password).then(hash => { console.log(`hash: ${hash}`); return compare(password, hash); }).then(res => { console.log(`right pwd: ${res}`); }).catch(error => { console.error(error); });
Ciphers
El cifrado nos permite codificar y decodificar una cadena por medio de una contraseña, es muy usado para proteger datos delicados en una base de datos como el identificador de seguridad social o una tarjeta de crédito, a diferencia del hash donde básicamente el cliente tiene el control (el usuario es el único que conoce su clave), aquí podemos internamente manejar este método de seguridad con algunas claves solo conocidas por el servidor.
En el ejemplo se muestra como codificar y decodificar usando una llave codificada con un salt y una contraseña y otra clave llamada vector inicializador, estos dos datos siempre deben cambiar, por lo que es interesante analizar servicios para manejar llaves secretas (aws secret manager, azure key vault, etc).
const crypto = require('crypto');
const algorithm = 'aes-256-cbc';
const password = 'mypasswd'; // must be variable env
// The salt should be as unique as possible.
// It is recommended that a salt is random and at least 16 bytes long
const salt = crypto.randomBytes(32);
// Scrypt is a password-based key derivation function that is designed to be expensive computationally
// and memory-wise in order to make brute-force attacks unrewarding
const key = crypto.scryptSync(password, salt, 32);
const iv = crypto.randomBytes(16);
function encrypt(plainData, algorithm, key, iv) {
try {
const cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(plainData, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
} catch (error) {
return new Error('wrong encrypt')
}
}
function decrypt(cipherData, algorithm, key, iv) {
try {
const decipher = crypto.createDecipheriv(algorithm, key, iv);
let decrypt = decipher.update(cipherData, 'hex', 'utf8');
decrypt = decipher.final('utf8');
return decrypt;
} catch (error) {
return new Error('wrong decrypt');
}
}
// test
let creditCard = '111-222-333';
const encryptCreditCard = encrypt(creditCard, algorithm, key, iv);
console.log(`Cipheriv: ${encryptCreditCard}`);
const decryptCreditCard = decrypt(encryptCreditCard, algorithm, key, iv);
console.log(`Decipheriv: ${decryptCreditCard}`);
Verificación a dos pasos
La verificación a dos pasos significa que además de proporcionar nuestra contraseña, va existir otro método para autenticar el usuario y puede ser desde enviar un código a un dispositivo como un celular o un dispositivo bancario hasta requerir algún método biométrico como la huella dactilar o reconocimiento facial.
-
Speakeasy
Speakeasy es un paquete para implementar un TOPT (Time-based One-time Password algorithm), el cual nos permite tener un segundo paso para autenticarnos, por medio de una aplicación o de un dispositivo. algunas aplicaciones comunes son: google authenticator, vip access, okta verify, los generadores de clave bancarios.
const speakeasy = require('speakeasy'); const QRCode = require('qrcode'); app.post('/login', (req, res) => { user.isLogin = true; user.secret = speakeasy.generateSecret(); res.send(JSON.stringify(user)); }) app.get('/qr', isAuth, (req, res) => { QRCode.toDataURL(req.user.secret.otpauth_url, function (err, data_url) { res.send(JSON.stringify(data_url)); }); }); app.post('/verify', isAuth, (req, res) => { const verify = speakeasy.totp.verify({ secret: req.user.secret.base32, encoding: 'base32', token: req.body.token }); res.send(JSON.stringify(verify)); })En el ejemplo anterior se creo una API donde vamos a autenticarnos y asi obtener toda la configuración de speakeasy y con esta podremos generar un código QR que vamos a escanear con una aplicación (Google Authenticator) donde vamos a obtener un código de autenticación de corta duración, este código lo vamos a poder validar y si nos retorna true, significa que podemos dejar acceder al usuario.
Deffie
El algoritmo Diffie-Hellman es muy utilizado para crear un llave en dos extremos sin que un intermediario pueda apropiarse de esta llave. Cuando usamos un protocolo de transporte seguro como https este internamente hace algo similar, donde se establece una comunicación (TLS) para que los interlocutores tengan la misma llave sin ser transportada por el medio y así poder codificar y decodificar el contenido de los mensajes transportados.
const crypto = require('crypto');
const assert = require('assert');
// Generate Alice's keys...
const alice = crypto.createDiffieHellman(2048);
const aliceKey = alice.generateKeys();
// Generate Bob's keys...
const bob = crypto.createDiffieHellman(alice.getPrime(), alice.getGenerator());
const bobKey = bob.generateKeys();
// Exchange and generate the secret...
const aliceSecret = alice.computeSecret(bobKey);
const bobSecret = bob.computeSecret(aliceKey);
console.log(aliceSecret.toString('hex'));
console.log(bobSecret.toString('hex'));
// OK
assert.strictEqual(aliceSecret.toString('hex'), bobSecret.toString('hex'));
En el ejemplo anterior tenemos dos extremos podemos llamarlo un cliente y un servidor, antes empezar a comunicarse debe existir un proceso de handshake donde el cliente envia un llave al servidor y el servidor a el cliente y apartir de un algoritmo y esta combinación de llaves se crea una llave en cada extremo que deben ser iguales, una vez se tenga esta llave se puede codificar el mensaje a transportar.