fix(security): don't reuse IV for cryptography
This commit is contained in:
committed by
Kalista Payne
parent
74fc543ef2
commit
07275bd522
@@ -75,7 +75,6 @@
|
|||||||
"S3_ACCESS_KEY_ID": "accessKeyId",
|
"S3_ACCESS_KEY_ID": "accessKeyId",
|
||||||
"S3_BUCKET": "bucket",
|
"S3_BUCKET": "bucket",
|
||||||
"S3_SECRET_ACCESS_KEY": "secretAccessKey",
|
"S3_SECRET_ACCESS_KEY": "secretAccessKey",
|
||||||
"SESSION_SECRET_IV": "12345678912345678912345678912345",
|
|
||||||
"SESSION_SECRET_KEY": "1234567891234567891234567891234567891234567891234567891234567891",
|
"SESSION_SECRET_KEY": "1234567891234567891234567891234567891234567891234567891234567891",
|
||||||
"SESSION_SECRET": "YOUR SECRET HERE",
|
"SESSION_SECRET": "YOUR SECRET HERE",
|
||||||
"SITE_HTTP_AUTH_ENABLED": "false",
|
"SITE_HTTP_AUTH_ENABLED": "false",
|
||||||
|
|||||||
@@ -1,26 +1,46 @@
|
|||||||
import {
|
import {
|
||||||
createCipheriv,
|
createCipheriv,
|
||||||
createDecipheriv,
|
createDecipheriv,
|
||||||
|
randomBytes,
|
||||||
} from 'crypto';
|
} from 'crypto';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
|
|
||||||
const algorithm = 'aes-256-ctr';
|
const ALGORITHM = 'aes-256-gcm';
|
||||||
|
const IV_LENGTH_BYTES = 12; // 96-bit nonce per NIST guidance for GCM
|
||||||
|
const AUTH_TAG_LENGTH_BYTES = 16; // 128-bit authentication tag
|
||||||
const SESSION_SECRET_KEY = nconf.get('SESSION_SECRET_KEY');
|
const SESSION_SECRET_KEY = nconf.get('SESSION_SECRET_KEY');
|
||||||
const SESSION_SECRET_IV = nconf.get('SESSION_SECRET_IV');
|
|
||||||
|
|
||||||
const key = Buffer.from(SESSION_SECRET_KEY, 'hex');
|
const key = Buffer.from(SESSION_SECRET_KEY, 'hex');
|
||||||
const iv = Buffer.from(SESSION_SECRET_IV, 'hex');
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt a UTF-8 string using AES-256-GCM and return iv|ciphertext|tag as hex.
|
||||||
|
* A fresh nonce is generated for every message to avoid keystream reuse, and
|
||||||
|
* the auth tag ensures forged payloads are rejected at the trust boundary.
|
||||||
|
*/
|
||||||
export function encrypt (text) {
|
export function encrypt (text) {
|
||||||
const cipher = createCipheriv(algorithm, key, iv);
|
const iv = randomBytes(IV_LENGTH_BYTES);
|
||||||
let crypted = cipher.update(text, 'utf8', 'hex');
|
const cipher = createCipheriv(ALGORITHM, key, iv);
|
||||||
crypted += cipher.final('hex');
|
const ciphertext = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
|
||||||
return crypted;
|
const authTag = cipher.getAuthTag();
|
||||||
|
return Buffer.concat([iv, ciphertext, authTag]).toString('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt an AES-256-GCM payload previously produced by encrypt().
|
||||||
|
* The layout is iv (12B) || ciphertext || authTag (16B), all hex encoded.
|
||||||
|
*/
|
||||||
export function decrypt (text) {
|
export function decrypt (text) {
|
||||||
const decipher = createDecipheriv(algorithm, key, iv);
|
const payload = Buffer.from(text, 'hex');
|
||||||
let dec = decipher.update(text, 'hex', 'utf8');
|
if (payload.length <= IV_LENGTH_BYTES + AUTH_TAG_LENGTH_BYTES) {
|
||||||
dec += decipher.final('utf8');
|
throw new Error('Encrypted payload is malformed');
|
||||||
return dec;
|
}
|
||||||
|
|
||||||
|
const iv = payload.subarray(0, IV_LENGTH_BYTES);
|
||||||
|
const authTag = payload.subarray(payload.length - AUTH_TAG_LENGTH_BYTES);
|
||||||
|
const ciphertext = payload.subarray(IV_LENGTH_BYTES, payload.length - AUTH_TAG_LENGTH_BYTES);
|
||||||
|
|
||||||
|
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
||||||
|
decipher.setAuthTag(authTag);
|
||||||
|
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
||||||
|
return decrypted.toString('utf8');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user