2

I've read the docs, but there's wayyyyy more going on than I can see any reason for. So either I'm doing this wrong, or the design is more complicated than necessary. I'm looking for two basic functions:

const key = "my_secret_key";
const encrypted = encrypt("secret", key);
const decrypted = decrypt(encrypted, key);

So far, this is what I've got, but it doesn't work and is ugly to boot:

const Encrypt = createCipheriv("aes-128-ccm", ENV.jwt_secret, null);
const Decrypt = createDecipheriv("aes-128-ccm", ENV.jwt_secret, null);

let encrypted = Encrypt.update("secret") + "" + Encrypt.final();
let decrypted = Decrypt.update(encrypted) + "" + Decrypt.final(); // throws error about not liking strings

I get the idea behind update for buffers... but I don't have a buffer. And I can't seem to figure out why final() returns anything, if update() does as well. What's the difference between their outputs? Do I ever have to call final? Can I just keep using the same Cipher?


How can I simply encrypt/decrypt a string using the node js crypto module?

Seph Reed
  • 8,797
  • 11
  • 60
  • 125
  • 1
    One does not simple encrypt and decrypt something. There are different ways of using encryption depending on the problem you are trying to solve. – Alexander O'Mara Dec 19 '19 at 18:47
  • 1
    Encrypt and decrypt a string with literally a Ceaser Cipher and I'll be happy. Just need a string that is hard to decipher without tools or the key. – Seph Reed Dec 19 '19 at 18:48
  • 1
    Node's crypto module is intended for lower-level / fine grained usage. For something with a high level API, roll your own helper functions or use something like https://www.npmjs.com/package/simple-crypto-js – djfdev Dec 19 '19 at 18:53
  • 1
    Refer to this answer: https://stackoverflow.com/a/53573115/1235935 – Saptarshi Basu Dec 19 '19 at 18:56

1 Answers1

1

node modules for this do exist (https://www.npmjs.com/package/simple-crypto-js), but they don't offer much in terms of customization. Also, I'm not a fan of importing a module which could just be a copy pasted 100 line file in my project.

@Saptarshi Basu offered this link (stackoverflow.com/a/53573115/1235935). I basically just copied that code and made something which works for the cipher type I'm using, and would take very little work to get the rest of the way.

Here it is, hope it helps someone:

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 function createKeyForCipher(cipherType: CipherType): string {
    let numBytes: number;
    switch (cipherType) {
        case "aes-128-gcm": numBytes = 128 / 8; break;
        default: throw new Error(`TODO: support cipherType "${cipherType}"`);
    }
    return crypto.randomBytes(numBytes).toString("base64");
}

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,
            { 'authTagLength': numAuthTagBytes } as any
        );

        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 = 24; // TODO: compute from numAuthTagBytes and stringBase
        let ivCharLength: number = 16; // TODO: compute from numIvBytes and stringBase

        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("");
    }
}

// ----------------------- Usage -----------------

const keyIn = createKeyForCipher("aes-128-gcm");
console.log(keyIn);
const cipher = new Cipher(keyIn, {
    type: "aes-128-gcm"
});
const encrypted = cipher.encrypt("This is some string to encrypt");
console.log(encrypted + "");
console.log(cipher.decrypt(encrypted));
Seph Reed
  • 8,797
  • 11
  • 60
  • 125