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?

4
  • 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. Commented Dec 19, 2019 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. Commented Dec 19, 2019 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 npmjs.com/package/simple-crypto-js Commented Dec 19, 2019 at 18:53
  • 1
    Refer to this answer: stackoverflow.com/a/53573115/1235935 Commented Dec 19, 2019 at 18:56

1 Answer 1

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));
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.