Skip to content

Commit 07275bd

Browse files
Corban VillaSabreCat
authored andcommitted
fix(security): don't reuse IV for cryptography
1 parent 74fc543 commit 07275bd

File tree

2 files changed

+31
-12
lines changed

2 files changed

+31
-12
lines changed

config.json.example

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@
7575
"S3_ACCESS_KEY_ID": "accessKeyId",
7676
"S3_BUCKET": "bucket",
7777
"S3_SECRET_ACCESS_KEY": "secretAccessKey",
78-
"SESSION_SECRET_IV": "12345678912345678912345678912345",
7978
"SESSION_SECRET_KEY": "1234567891234567891234567891234567891234567891234567891234567891",
8079
"SESSION_SECRET": "YOUR SECRET HERE",
8180
"SITE_HTTP_AUTH_ENABLED": "false",

website/server/libs/encryption.js

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,46 @@
11
import {
22
createCipheriv,
33
createDecipheriv,
4+
randomBytes,
45
} from 'crypto';
56
import nconf from 'nconf';
67

7-
const algorithm = 'aes-256-ctr';
8+
const ALGORITHM = 'aes-256-gcm';
9+
const IV_LENGTH_BYTES = 12; // 96-bit nonce per NIST guidance for GCM
10+
const AUTH_TAG_LENGTH_BYTES = 16; // 128-bit authentication tag
811
const SESSION_SECRET_KEY = nconf.get('SESSION_SECRET_KEY');
9-
const SESSION_SECRET_IV = nconf.get('SESSION_SECRET_IV');
1012

1113
const key = Buffer.from(SESSION_SECRET_KEY, 'hex');
12-
const iv = Buffer.from(SESSION_SECRET_IV, 'hex');
1314

15+
/**
16+
* Encrypt a UTF-8 string using AES-256-GCM and return iv|ciphertext|tag as hex.
17+
* A fresh nonce is generated for every message to avoid keystream reuse, and
18+
* the auth tag ensures forged payloads are rejected at the trust boundary.
19+
*/
1420
export function encrypt (text) {
15-
const cipher = createCipheriv(algorithm, key, iv);
16-
let crypted = cipher.update(text, 'utf8', 'hex');
17-
crypted += cipher.final('hex');
18-
return crypted;
21+
const iv = randomBytes(IV_LENGTH_BYTES);
22+
const cipher = createCipheriv(ALGORITHM, key, iv);
23+
const ciphertext = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
24+
const authTag = cipher.getAuthTag();
25+
return Buffer.concat([iv, ciphertext, authTag]).toString('hex');
1926
}
2027

28+
/**
29+
* Decrypt an AES-256-GCM payload previously produced by encrypt().
30+
* The layout is iv (12B) || ciphertext || authTag (16B), all hex encoded.
31+
*/
2132
export function decrypt (text) {
22-
const decipher = createDecipheriv(algorithm, key, iv);
23-
let dec = decipher.update(text, 'hex', 'utf8');
24-
dec += decipher.final('utf8');
25-
return dec;
33+
const payload = Buffer.from(text, 'hex');
34+
if (payload.length <= IV_LENGTH_BYTES + AUTH_TAG_LENGTH_BYTES) {
35+
throw new Error('Encrypted payload is malformed');
36+
}
37+
38+
const iv = payload.subarray(0, IV_LENGTH_BYTES);
39+
const authTag = payload.subarray(payload.length - AUTH_TAG_LENGTH_BYTES);
40+
const ciphertext = payload.subarray(IV_LENGTH_BYTES, payload.length - AUTH_TAG_LENGTH_BYTES);
41+
42+
const decipher = createDecipheriv(ALGORITHM, key, iv);
43+
decipher.setAuthTag(authTag);
44+
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
45+
return decrypted.toString('utf8');
2646
}

0 commit comments

Comments
 (0)