@directus/api
Version:
Directus is a real-time API and App dashboard for managing SQL database content
65 lines (64 loc) • 2.56 kB
JavaScript
import crypto from 'node:crypto';
import { promisify } from 'node:util';
const VERSION = '1';
const KDF = 'scrypt';
const SCRYPT_DEFAULTS = { N: 2 ** 14, r: 8, p: 1 }; // ~16MB mem, good server default
const scryptAsync = promisify(crypto.scrypt);
const deriveKey = async (password, salt, opts = SCRYPT_DEFAULTS) => {
return await scryptAsync(password, salt, 32, opts);
};
export const encrypt = async (plainText, password) => {
const salt = crypto.randomBytes(16);
const keyBuf = await deriveKey(password, salt, SCRYPT_DEFAULTS);
// Generate a 12-byte IV for GCM and keep base64 string for storage
const ivBuf = crypto.randomBytes(12);
const iv = ivBuf.toString('base64');
const cipher = crypto.createCipheriv('aes-256-gcm', keyBuf, ivBuf);
let cipherText = cipher.update(plainText, 'utf8', 'base64');
cipherText += cipher.final('base64');
const tag = cipher.getAuthTag().toString('base64');
// 1||scrypt||N||r||p||salt||iv||cipherText||tag
return [
VERSION,
KDF,
SCRYPT_DEFAULTS.N,
SCRYPT_DEFAULTS.r,
SCRYPT_DEFAULTS.p,
salt.toString('base64'),
iv,
cipherText,
tag,
].join('||');
};
export const decrypt = async (encryptedText, password) => {
const parts = encryptedText.split('||');
if (parts.length < 9)
throw new Error('Invalid encrypted payload');
const [version, kdf, nStr, rStr, pStr, saltB64, ivB64, cipherText, tagB64] = parts;
if (version !== VERSION)
throw new Error(`Unsupported version: ${version}`);
if (kdf !== KDF)
throw new Error(`Unsupported kdf: ${kdf}`);
if (!saltB64)
throw new Error('No salt in encrypted string');
if (!ivB64)
throw new Error('No iv in encrypted string');
if (cipherText === undefined)
throw new Error('No cipherText in encrypted string');
if (!tagB64)
throw new Error('No tag in encrypted string');
const opts = {
N: Number(nStr) || SCRYPT_DEFAULTS.N,
r: Number(rStr) || SCRYPT_DEFAULTS.r,
p: Number(pStr) || SCRYPT_DEFAULTS.p,
};
const salt = Buffer.from(saltB64, 'base64');
const keyBuf = await deriveKey(password, salt, opts);
const iv = Buffer.from(ivB64, 'base64');
const tag = Buffer.from(tagB64, 'base64');
const decipher = crypto.createDecipheriv('aes-256-gcm', keyBuf, iv);
decipher.setAuthTag(tag);
let plaintext = decipher.update(cipherText, 'base64', 'utf8');
plaintext += decipher.final('utf8');
return plaintext;
};