Skip to content

Instantly share code, notes, and snippets.

@rjz
Last active March 11, 2024 20:11
Show Gist options
  • Star 54 You must be signed in to star a gist
  • Fork 18 You must be signed in to fork a gist
  • Save rjz/15baffeab434b8125ca4d783f4116d81 to your computer and use it in GitHub Desktop.
Save rjz/15baffeab434b8125ca4d783f4116d81 to your computer and use it in GitHub Desktop.
example using node.js crypto API with aes-256-gcm
const buffer = require('buffer');
const crypto = require('crypto');
// Demo implementation of using `aes-256-gcm` with node.js's `crypto` lib.
const aes256gcm = (key) => {
const ALGO = 'aes-256-gcm';
// encrypt returns base64-encoded ciphertext
const encrypt = (str) => {
// The `iv` for a given key must be globally unique to prevent
// against forgery attacks. `randomBytes` is convenient for
// demonstration but a poor way to achieve this in practice.
//
// See: e.g. https://csrc.nist.gov/publications/detail/sp/800-38d/final
const iv = new Buffer(crypto.randomBytes(12), 'utf8');
const cipher = crypto.createCipheriv(ALGO, key, iv);
// Hint: Larger inputs (it's GCM, after all!) should use the stream API
let enc = cipher.update(str, 'utf8', 'base64');
enc += cipher.final('base64');
return [enc, iv, cipher.getAuthTag()];
};
// decrypt decodes base64-encoded ciphertext into a utf8-encoded string
const decrypt = (enc, iv, authTag) => {
const decipher = crypto.createDecipheriv(ALGO, key, iv);
decipher.setAuthTag(authTag);
let str = decipher.update(enc, 'base64', 'utf8');
str += decipher.final('utf8');
return str;
};
return {
encrypt,
decrypt,
};
};
const KEY = new Buffer(crypto.randomBytes(32), 'utf8');
const aesCipher = aes256gcm(KEY);
const [encrypted, iv, authTag] = aesCipher.encrypt('hello, world');
const decrypted = aesCipher.decrypt(encrypted, iv, authTag);
console.log(decrypted); // 'hello, world'
@meSmashsta
Copy link

meSmashsta commented Dec 19, 2020

Hi the decipher.setAuthTag(authTag) is commented out in node v14.15.2, how do I go about this? And so when I try to decrypt I get the error:
TypeError: decipher.setAuthTag is not a function

EDIT: fixed it, I was using createCipheriv instead of createDecipheriv, thanks for your article.

@coryhardman
Copy link

The commend: IV should be unique but necessarily random sort of leads one to the wrong understanding of how significant the IV is for GCM. See this article: https://crypto.stackexchange.com/questions/26790/how-bad-it-is-using-the-same-iv-twice-with-aes-gcm

Also note that 16 byte nonce is not recommended for GCM, instead a 12 byte (96bit) one should be used

@rjz
Copy link
Author

rjz commented May 2, 2021

Good callouts—thanks, @coryhardman! I've updated the gist to (hopefully) add a little more gravity around IV construction.

@tolgaatam
Copy link

tolgaatam commented May 13, 2021

I would like to make a comment how crypto.randomBytes is a poor way for IV generation.

As far as I know, crypto.randomBytes uses dev/urandom or getRandom() under the hood. These are both "okay" sources of randomness. If a developer trusts that these sources yield to a unique IV every time, then this is bad practice. But your comment would make folks believe that crypto.randomBytes is a bad source. But the problem is not that. Indeed, using random IV's for GCM mode is a bad choice, if no other precautions are taken. The developer can use crypto.randomBytes or any other PRNG for IV's if they store them such that the developer or the storage mechanism enforce a unique constraint on IVs. Another way could be having separate keys for each encryption such that the uniqueness (or non-uniqueness) of IVs becomes unimportant (because it is the IV-key pair that must be unique in GCM).

@coryhardman
Copy link

The key requirement for the IV in GCM is that the IV is unique per message using the same key. See section 8.2 from Nist on how they recommend constructing the IV.

Using a random IV can be okay. It is just important to note that 96 bits is rather small space to generate random values in (after 2^48 messages you'd have a 50% chance of IV reuse). Nist recommends simply never using the same key for more than 2^32 messages to be sure this risk won't happen. In such case, a random IV will be okay to use if it is truly a random IV.

You can also deterministically come up with an IV if your situation supports it. For example using timestamps. This design however needs to be sure to generate a unique IV, so if two services might encrypt a message with the same key at the same time then there would be IV reuse.

Ultimately using GCM in practice is hard because of the IV. It is very critical that folks understand this when they choose to use GCM and plan accordingly.

@tolgaatam
Copy link

I agree with your points @coryhardman , GCM is not a method that can blindly be thrown into a project. Yes, it is quite a secure one, but it is a "double-edged sword" per se. Only with a well-implemented key and IV generation process, it shines.

@nielsnl68
Copy link

Reading the above, i'm wondering how to send the encrypted message over the internet?
Is it save to send all 3 of values or do we need to send the tag an other way?

@hardikdgsa
Copy link

hardikdgsa commented Oct 5, 2021

Reading the above, i'm wondering how to send the encrypted message over the internet? Is it save to send all 3 of values or do we need to send the tag an other way?

@nielsnl68 Did you find any better option for this? to send this over the internet?

@nielsnl68
Copy link

I did not implemented it, and stick to the CBC variant for now. Also, I'm not an expert and don't dear to advice anything.

@ronnieroyston
Copy link

Most applications should consider using the new KeyObject API instead of passing keys as strings or 
Buffers due to improved security features. -https://nodejs.org/api/crypto.html#class-keyobject

e.g. generateKey('aes', { length: 256 }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment