crypto-shelf
Version:
Library collection for password hashing, HMAC-based signature generation, and symmetric encryption. Build on top of Node's crypto module
113 lines (87 loc) • 2.99 kB
JavaScript
import {
randomBytes,
getCipherInfo,
scrypt,
createCipheriv,
createDecipheriv,
} from 'node:crypto';
import { promisify } from 'node:util';
import { merge } from './util.js';
const scryptAsync = promisify(scrypt);
const DEFAULT_SALT = Buffer.from('Eu372mYdUcdOzo2KrCdav2twN5CbTcmzGnvRKxxv5Rc', 'base64');
const AUTHENTICATED_ENCRYPTION_MODES = new Set(['gcm', 'ccm', 'ocb']);
const AUTHENTICATED_ENCRYPTION_CIPHERS = new Set(['chacha20-poly1305']);
export const defaults = {
algorithm: 'aes-256-gcm', // Use aes-*-gcm, chacha20-poly1305 or aes-*-ocb for strong encryption
encoding: 'base64url',
aad: null, // additional authentication data
};
export function generateSalt(options = {}) {
const { encoding, saltLength = 32 } = merge(defaults, options);
const salt = randomBytes(saltLength);
return encoding ? salt.toString(encoding) : salt;
}
export async function generateKey(passphrase, salt = null, options = {}) {
const { algorithm, encoding } = merge(defaults, options);
if (salt && !Buffer.isBuffer(salt)) salt = Buffer.from(salt, encoding);
if (!salt) salt = DEFAULT_SALT;
const { keyLength } = getCipherInfo(algorithm);
return await scryptAsync(
passphrase,
salt,
keyLength,
);
}
export function encrypt(key, str, options = {}) {
const { algorithm, encoding, aad } = merge(defaults, options);
const { ivLength, authTagLength, mode } = getInfo(algorithm);
const iv = randomBytes(ivLength);
const cipher = createCipheriv(algorithm, key, iv, { authTagLength });
if (mode === 'ccm') {
cipher.setAutoPadding(false);
}
if (authTagLength && aad) {
cipher.setAAD(Buffer.from(aad, 'utf8'), {
plaintextLength: Buffer.byteLength(str),
});
}
const encrypted = Buffer.concat([
iv,
cipher.update(str, 'utf8'),
cipher.final(),
authTagLength ? cipher.getAuthTag() : Buffer.alloc(0),
]);
return encoding ? encrypted.toString(encoding) : encrypted;
}
export function decrypt(key, str, options = {}) {
const { algorithm, encoding, aad } = merge(defaults, options);
const { ivLength, authTagLength, mode } = getInfo(algorithm);
const encrypted = Buffer.from(str, encoding);
const iv = encrypted.subarray(0, ivLength);
const decipher = createDecipheriv(algorithm, key, iv, { authTagLength });
if (mode === 'ccm') {
decipher.setAutoPadding(false);
}
if (authTagLength && aad) {
decipher.setAAD(Buffer.from(aad, 'utf8'), {
plaintextLength: encrypted.length - ivLength - authTagLength,
});
}
if (authTagLength) decipher.setAuthTag(encrypted.subarray(encrypted.length - authTagLength));
return decipher.update(
encrypted.subarray(ivLength, encrypted.length - authTagLength),
'utf8',
) + decipher.final('utf8');
}
function getInfo(algorithm) {
const { ivLength, mode } = getCipherInfo(algorithm);
const usingAuth = (
AUTHENTICATED_ENCRYPTION_MODES.has(mode)
|| AUTHENTICATED_ENCRYPTION_CIPHERS.has(algorithm)
);
return {
ivLength: ivLength || 0,
authTagLength: usingAuth ? 16 : 0,
mode,
};
}