2

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.

Marc
  • 2,920
  • 3
  • 14
  • 30
  • Does this answer yopur question https://stackoverflow.com/questions/37997354/evp-decryptfinal-exbad-decrypt-when-using-node-js – Grumpy Nov 24 '21 at 09:36
  • @Grumpy No, because the encoding/decoding is done through buffer objects. See my edited post. Event with explicit specification, it does not work. – Marc Nov 24 '21 at 09:56
  • 2
    The correct way is to generate a random salt during encryption (as it already happens) and then pass this salt to the decrypting side. Like the IV, the salt is not secret and is usually concatenated with the ciphertext, e.g. salt|iv|ciphertext. By the way, a separator is not necessary because both sides can perform the separation based on the lengths. – Topaco Nov 24 '21 at 11:20
  • @Topaco Thank you very much for the hint. Works very well. If you want to post a answer, i would accept it. BTW. The separator was just for "debugging", to have a visual indicator. – Marc Nov 24 '21 at 11:29

0 Answers0