Search code examples
node.jslinuxnode-crypto

Node JS decipher not working on Ubuntu server


I have a Cipher which can takes a key. It can then "encrypt" and "decrypt" strings.

This is the error:

Error: Unsupported state or unable to authenticate data
0|ts-node  |     at Decipheriv.final (crypto.js:183:26)

This is my implementation:

import crypto from "crypto";


export type CipherType = "aes-128-gcm" | "aes-128-ccm" | "aes-192-gcm" | "aes-192-ccm" | "aes-256-gcm" | "aes-256-ccm";

export class Cipher {
    constructor(private key: string, private config: {
        type: CipherType,
        numAuthTagBytes?: number,
        numIvBytes?: number,
        stringBase?: "base64",
    }) {
        config.numAuthTagBytes = config.numAuthTagBytes || 16;
        config.numIvBytes = config.numIvBytes || 12;
        config.stringBase = config.stringBase || "base64";
        if (config.numAuthTagBytes < 16) { console.warn(`Be careful of short auth tags`); }
        if (config.numIvBytes < 12) { console.warn(`Be careful of short ivs`); }
    }


    public encrypt(msg: string) {
        const {type, numIvBytes, numAuthTagBytes, stringBase} = this.config;
        const iv = crypto.randomBytes(numIvBytes);
        const cipher = crypto.createCipheriv(
            type,
            Buffer.from(this.key, stringBase),
            iv,
            numAuthTagBytes ? { 'authTagLength': numAuthTagBytes } as any : undefined
        );

        return [
            iv.toString(stringBase),
            cipher.update(msg, "utf8", stringBase),
            cipher.final(stringBase),
            (cipher as any).getAuthTag().toString(stringBase)
        ].join("");
    }


    public decrypt(cipherText: string) {
        const {type, numIvBytes, numAuthTagBytes, stringBase} = this.config;
        let authTagCharLength: number = this.bytesToChars(numAuthTagBytes);
        let ivCharLength: number = this.bytesToChars(numIvBytes);

        const authTag = Buffer.from(cipherText.slice(-authTagCharLength), stringBase);
        const iv = Buffer.from(cipherText.slice(0, ivCharLength), stringBase);
        const encryptedMessage = Buffer.from(cipherText.slice(ivCharLength, -authTagCharLength), stringBase);

        const decipher = crypto.createDecipheriv(
            type,
            Buffer.from(this.key, stringBase),
            iv,
            { 'authTagLength': numAuthTagBytes } as any
        );
        (decipher as any).setAuthTag(authTag);

        return [
            decipher.update(encryptedMessage, stringBase, "utf8"),
            decipher.final()
        ].join("");
    }

    private bytesToChars(numBytes) {
        if (this.config.stringBase === "base64") {
            switch (numBytes) {
                case 16: return 24;
                case 12: return 16;
                case 8: return 12;
                case 4: return 8;
                case 0: return 0;
                default: throw new Error("What's the math here?");
            }
        } else {
            throw new Error("TODO: support other string types");
        }
    }
}

It works great locally (MacOS), but on the Ubuntu server it fails to decrypt every time.


What am I doing wrong? Does Linux perhaps treat strings differently?


EDIT: I think I've found the issue node -v -> v8.10.0


Solution

  • Ubuntu typically has a very old version NodeJS. You should update your node version:

    (pulled from https://askubuntu.com/a/548776/461996)

    # Using Ubuntu
    curl -sL https://deb.nodesource.com/setup_13.x | sudo -E bash -
    sudo apt-get install -y nodejs
    

    where 13 is the version you'd like (probably should be newest version)