Changed database file encryption to pbkdf2 for key derivation and aes-256-gcm for encryption (#6296)
* Added pbkdf2 and aes-256-gcm options for database file encryption * Added dbCipherAlgorithm option * Changed pbkdf2 to default Maintains backward compatibility, but will require a manual repush to update to the new version * Removed dbkeyderivationiterations option, as this branch is to be more opinionated
Josiah Baldwin committed
Aug 4, 2024 at 16:02 UTC
5b76a31644419b562ee2768d2976c53dcdb3d545
1 file changed
+48
-10
db.js
+48
-10
@@ -417,34 +417,72 @@ module.exports.CreateDB = function (parent, func) {
417
};
418
419
// Get encryption key
420
- obj.getEncryptDataKey = function (password) {
420
+ obj.getEncryptDataKey = function (password, salt, iterations) {
421
+ if (typeof password != 'string') return null;
422
+ let key;
423
+ try {
424
+ key = parent.crypto.pbkdf2Sync(password, salt, iterations, 32, 'sha384');
425
+ } catch (e) {
426
+ // If this previous call fails, it's probably because older pbkdf2 did not specify the hashing function, just use the default.
427
+ key = parent.crypto.pbkdf2Sync(password, salt, iterations, 32);
428
+ }
429
+ return key
430
+ }
431
+
432
+ obj.oldGetEncryptDataKey = function (password) {
433
if (typeof password != 'string') return null;
434
return parent.crypto.createHash('sha384').update(password).digest("raw").slice(0, 32);
435
}
436
437
// Encrypt data
438
obj.encryptData = function (password, plaintext) {
427
- var key = obj.getEncryptDataKey(password);
428
- if (key == null) return null;
439
+ let encryptionVersion = 0x1;
440
+ let iterations = 100000
441
const iv = parent.crypto.randomBytes(16);
430
- const aes = parent.crypto.createCipheriv('aes-256-cbc', key, iv);
442
+ var key = obj.getEncryptDataKey(password, iv, iterations);
443
+ if (key == null) return null;
444
+ const aes = parent.crypto.createCipheriv("aes-256-gcm", key, iv);
445
var ciphertext = aes.update(plaintext);
432
- ciphertext = Buffer.concat([iv, ciphertext, aes.final()]);
446
+ let versionbuf = Buffer.allocUnsafe(2);
447
+ versionbuf.writeUInt16BE(encryptionVersion);
448
+ let iterbuf = Buffer.allocUnsafe(4);
449
+ iterbuf.writeUInt32BE(iterations);
450
+ let encryptedBuf = aes.final();
451
+ ciphertext = Buffer.concat([versionbuf, iterbuf, aes.getAuthTag(), iv, ciphertext, encryptedBuf]);
452
return ciphertext.toString('base64');
453
}
454
455
// Decrypt data
456
obj.decryptData = function (password, ciphertext) {
457
+ let ciphertextBytes = Buffer.from(ciphertext, 'base64');
458
try {
439
- var key = obj.getEncryptDataKey(password);
440
- if (key == null) return null;
441
- const ciphertextBytes = Buffer.from(ciphertext, 'base64');
459
const iv = ciphertextBytes.slice(0, 16);
460
const data = ciphertextBytes.slice(16);
444
- const aes = parent.crypto.createDecipheriv('aes-256-cbc', key, iv);
445
- var plaintextBytes = Buffer.from(aes.update(data));
461
+ let key = obj.oldGetEncryptDataKey(password);
462
+ const aes = parent.crypto.createDecipheriv("aes-256-cbc", key, iv);
463
+ let plaintextBytes = Buffer.from(aes.update(data));
464
plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
465
return plaintextBytes;
466
+ } catch (e) {}
467
+ // Adding an encryption version lets us avoid try catching in the future
468
+ let encryptionVersion = ciphertextBytes.readUInt16BE(0);
469
+ try {
470
+ switch (encryptionVersion) {
471
+ case 0x1:
472
+ let iterations = ciphertextBytes.readUInt32BE(2);
473
+ let authTag = ciphertextBytes.slice(6, 22);
474
+ const iv = ciphertextBytes.slice(22, 38);
475
+ const data = ciphertextBytes.slice(38);
476
+ let key = obj.getEncryptDataKey(password, iv, iterations);
477
+ if (key == null) return null;
478
+ const aes = parent.crypto.createDecipheriv("aes-256-gcm", key, iv);
479
+ aes.setAuthTag(authTag);
480
+ let plaintextBytes = Buffer.from(aes.update(data));
481
+ plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
482
+ return plaintextBytes;
483
+ default:
484
+ return null;
485
+ }
486
} catch (ex) { return null; }
487
}
488