Im working on a simple encrypt/decrypt function in node.js that allows me to encrypt a input text, save that output somewhere (database, filesystem, etc.) and decrypt the text later at any time again.
Seems like the functions per se are working.
But when i save the text, restart the node.js process and then try to decrypt it, i get a ERR_OSSL_EVP_BAD_DECRYPT
error:
internal/crypto/cipher.js:174
const ret = this[kHandle].final();
^
Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
at Decipheriv.final (internal/crypto/cipher.js:174:29)
at decrypt (/home/marc/projects/test-encryption/index.js:34:52)
at Object.<anonymous> (/home/marc/projects/test-encryption/index.js:51:13)
at Module._compile (internal/modules/cjs/loader.js:1118:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1138:10)
at Module.load (internal/modules/cjs/loader.js:982:32)
at Function.Module._load (internal/modules/cjs/loader.js:875:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
at internal/main/run_main_module.js:17:47 {
library: 'digital envelope routines',
function: 'EVP_DecryptFinal_ex',
reason: 'bad decrypt',
code: 'ERR_OSSL_EVP_BAD_DECRYPT'
}
const crypto = require("crypto");
const algorithm = "aes-256-cbc"; //Using AES encryption
// https://stackoverflow.com/a/53573115/5781499
// https://www.tutorialspoint.com/encrypt-and-decrypt-data-in-nodejs
const salt = crypto.randomBytes(16);
const key = crypto.scryptSync("Pa$$w0rd", salt, 32);
//const key = crypto.randomBytes(32);
const iv = crypto.randomBytes(16);
//Encrypting text
function encrypt(text) {
let cipher = crypto.createCipheriv(algorithm, Buffer.from(key), iv);
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return `${iv.toString("hex")}:${encrypted.toString("hex")}`;
}
// Decrypting text
function decrypt(text) {
let [ivs, data] = text.split(":");
let iv = Buffer.from(ivs, "hex");
let encryptedText = Buffer.from(data, "hex");
let decipher = crypto.createDecipheriv(algorithm, Buffer.from(key), iv);
let decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}
// Text send to encrypt function
var hw = encrypt("mySuperSecretString")
console.log(hw)
console.log(decrypt(hw))
Output for the code above:
78d8082330c1fccff980120c0a9d4f7f:1774582655bd0a732313d4d5aa7ed621baa875109d0d8ae562efbc58168bdd3e
mySuperSecretString
I save the output string, copy them back into the decrypt function and run the code again, like this:
console.log(decrypt("78d8082330c1fccff980120c0a9d4f7f:1774582655bd0a732313d4d5aa7ed621baa875109d0d8ae562efbc58168bdd3e"))
And the error message show up. Any clue what that means or how i can fix that?
Any help is welcome
EDIT: Event with the comment/post from @Grumpy, it does not work. Dropped every buffer thing in the code an specified the encoding explicit. Does not work.
//Encrypting text
function encrypt(text) {
let cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(text, "utf8", "hex");
//encrypted = Buffer.concat([encrypted, cipher.final()]);
encrypted += cipher.final("hex");
return `${iv.toString("hex")}:${encrypted}`;
}
// Decrypting text
function decrypt(text) {
let [ivs, data] = text.split(":");
let iv = Buffer.from(ivs, "hex");
//let encryptedText = Buffer.from(data, "hex");
let decipher = crypto.createDecipheriv(algorithm, key, iv);
let decrypted = decipher.update(data, "hex", "utf8");
//decrypted = Buffer.concat([decrypted, decipher.final("utf8")]);
decrypted += decipher.final("utf8");
return decrypted.toString();
}
// Text send to encrypt function
var hw = encrypt("mySuperSecretString")
console.log(hw)
console.log(decrypt(hw))
console.log(decrypt("2954c16406fb6f9b50f87972ff55aa66:30500b1d9e4c36f436df560bde3dbc8e36bcc7395ea62d8ea0f7a4f6b6627d00"))
(Hiden, because it has nothing to do with the question/error per se.)
EDIT2: I need to save the key too. Encode it as hex string,and convert it later back:
const key = Buffer.from("061bb1960ac837ab4efab2902a0a0a6f7370ec48db469b8c4c3a31899ca75788", "hex");
But this seems not right, should i not be able to decrypt the data without saveing the key? As the comment from @artjom-b says: https://stackoverflow.com/a/56319329/5781499
EDIT3: I think i found my problem. I use a random salt to generate the key. With this, the key changed every time and a decrpytion was not possible.
I keep this question open for answers. Perhaps some one can enlighten me how to do it properly, because encrpytion is a touchy topic.
EDIT4: Thanks to @Topaco, as mentionend in the comments, salt is not supposed to be "secret" and can be stored besides the iv & encrypted data. I came up with the following encrypt/decrypt functions:
process.env = Object.assign({
PASSWORD: "Pa$$w0rd"
}, process.env);
function encrypt(text) {
let iv = crypto.randomBytes(16);
let salt = crypto.randomBytes(16);
let key = crypto.scryptSync(process.env.PASSWORD, salt, 32);
let cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(text, "utf8", "hex");
encrypted += cipher.final("hex");
return `${iv.toString("hex")}:${salt.toString("hex")}:${encrypted}`;
}
function decrypt(text) {
let [ivs, salts, data] = text.split(":");
let iv = Buffer.from(ivs, "hex");
let salt = Buffer.from(salts, "hex");
let key = crypto.scryptSync(process.env.PASSWORD, salt, 32);
let decipher = crypto.createDecipheriv(algorithm, key, iv);
let decrypted = decipher.update(data, "hex", "utf8");
decrypted += decipher.final("utf8");
return decrypted.toString();
}
As mentioned, the mark (:
) was for me just to have a visual indicator. This can be replaced to be handled by length of iv/salt/etc.