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
This commit is contained in:
Josiah Baldwin 2024-08-04 16:02:22 -07:00 committed by GitHub
parent 41e4213fc5
commit 5b76a31644
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

58
db.js
View File

@ -417,34 +417,72 @@ module.exports.CreateDB = function (parent, func) {
};
// Get encryption key
obj.getEncryptDataKey = function (password) {
obj.getEncryptDataKey = function (password, salt, iterations) {
if (typeof password != 'string') return null;
let key;
try {
key = parent.crypto.pbkdf2Sync(password, salt, iterations, 32, 'sha384');
} catch (e) {
// If this previous call fails, it's probably because older pbkdf2 did not specify the hashing function, just use the default.
key = parent.crypto.pbkdf2Sync(password, salt, iterations, 32);
}
return key
}
obj.oldGetEncryptDataKey = function (password) {
if (typeof password != 'string') return null;
return parent.crypto.createHash('sha384').update(password).digest("raw").slice(0, 32);
}
// Encrypt data
obj.encryptData = function (password, plaintext) {
var key = obj.getEncryptDataKey(password);
if (key == null) return null;
let encryptionVersion = 0x1;
let iterations = 100000
const iv = parent.crypto.randomBytes(16);
const aes = parent.crypto.createCipheriv('aes-256-cbc', key, iv);
var key = obj.getEncryptDataKey(password, iv, iterations);
if (key == null) return null;
const aes = parent.crypto.createCipheriv("aes-256-gcm", key, iv);
var ciphertext = aes.update(plaintext);
ciphertext = Buffer.concat([iv, ciphertext, aes.final()]);
let versionbuf = Buffer.allocUnsafe(2);
versionbuf.writeUInt16BE(encryptionVersion);
let iterbuf = Buffer.allocUnsafe(4);
iterbuf.writeUInt32BE(iterations);
let encryptedBuf = aes.final();
ciphertext = Buffer.concat([versionbuf, iterbuf, aes.getAuthTag(), iv, ciphertext, encryptedBuf]);
return ciphertext.toString('base64');
}
// Decrypt data
obj.decryptData = function (password, ciphertext) {
let ciphertextBytes = Buffer.from(ciphertext, 'base64');
try {
var key = obj.getEncryptDataKey(password);
if (key == null) return null;
const ciphertextBytes = Buffer.from(ciphertext, 'base64');
const iv = ciphertextBytes.slice(0, 16);
const data = ciphertextBytes.slice(16);
const aes = parent.crypto.createDecipheriv('aes-256-cbc', key, iv);
var plaintextBytes = Buffer.from(aes.update(data));
let key = obj.oldGetEncryptDataKey(password);
const aes = parent.crypto.createDecipheriv("aes-256-cbc", key, iv);
let plaintextBytes = Buffer.from(aes.update(data));
plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
return plaintextBytes;
} catch (e) {}
// Adding an encryption version lets us avoid try catching in the future
let encryptionVersion = ciphertextBytes.readUInt16BE(0);
try {
switch (encryptionVersion) {
case 0x1:
let iterations = ciphertextBytes.readUInt32BE(2);
let authTag = ciphertextBytes.slice(6, 22);
const iv = ciphertextBytes.slice(22, 38);
const data = ciphertextBytes.slice(38);
let key = obj.getEncryptDataKey(password, iv, iterations);
if (key == null) return null;
const aes = parent.crypto.createDecipheriv("aes-256-gcm", key, iv);
aes.setAuthTag(authTag);
let plaintextBytes = Buffer.from(aes.update(data));
plaintextBytes = Buffer.concat([plaintextBytes, aes.final()]);
return plaintextBytes;
default:
return null;
}
} catch (ex) { return null; }
}