Skip to content

Commit d407ded

Browse files
fix(core): Derive encryption key with scrypt and warn on weak secret
Key derivation used a single unsalted SHA-256 of the secret, which is cheap to brute-force offline for a weak or low-entropy secret. Switch to scrypt, a memory-hard KDF, so each guess is expensive, and warn at startup when the secret is shorter than the recommended length. The salt is a fixed application constant, since the key must be derived synchronously at bootstrap. Relates to #2648
1 parent e2626c8 commit d407ded

1 file changed

Lines changed: 19 additions & 4 deletions

File tree

packages/core/src/config/system/default-encryption-strategy.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'crypto';
1+
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'crypto';
22

33
import { Logger } from '../logger/vendure-logger';
44

@@ -11,6 +11,14 @@ import { EncryptionStrategy } from './encryption-strategy';
1111
const CIPHERTEXT_PREFIX = 'enc:v1:';
1212
const ALGORITHM = 'aes-256-gcm';
1313
const IV_LENGTH = 12;
14+
/**
15+
* A fixed salt for key derivation. A per-database random salt would additionally defeat cross-install
16+
* precomputation, but deriving the key synchronously at bootstrap (as required by the synchronous
17+
* transformer) rules out loading a stored salt here; the primary protection against brute-force is
18+
* scrypt's per-guess cost combined with a high-entropy secret.
19+
*/
20+
const KEY_DERIVATION_SALT = 'vendure:default-encryption-strategy';
21+
const RECOMMENDED_SECRET_LENGTH = 24;
1422

1523
/**
1624
* @description
@@ -39,9 +47,16 @@ export class DefaultEncryptionStrategy implements EncryptionStrategy {
3947

4048
init() {
4149
if (this.options.secret) {
42-
// Derive a fixed-length 32-byte key from the provided secret, so that any-length
43-
// secrets are supported while satisfying AES-256's key-length requirement.
44-
this.key = createHash('sha256').update(this.options.secret, 'utf8').digest();
50+
if (this.options.secret.length < RECOMMENDED_SECRET_LENGTH) {
51+
Logger.warn(
52+
`The encryption secret is shorter than the recommended ${RECOMMENDED_SECRET_LENGTH} ` +
53+
'characters. Use a long, high-entropy random value: a short or guessable secret ' +
54+
'can be brute-forced offline if the encrypted data or key check is obtained.',
55+
);
56+
}
57+
// Derive a 32-byte AES-256 key from the secret using scrypt, a memory-hard KDF, so that
58+
// brute-forcing a weak secret is expensive per guess (a plain hash would be cheap).
59+
this.key = scryptSync(this.options.secret, KEY_DERIVATION_SALT, 32);
4560
}
4661
}
4762

0 commit comments

Comments
 (0)