Skip to content

Instantly share code, notes, and snippets.

@ronnieroyston
Created August 29, 2023 21:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ronnieroyston/5df0b1a119b4263f08cab2d6bd6e5425 to your computer and use it in GitHub Desktop.
Save ronnieroyston/5df0b1a119b4263f08cab2d6bd6e5425 to your computer and use it in GitHub Desktop.
JSON Web Encryption (JWE) Token Key Wrapping AES256-KW w AES-GCM w Node.js Crypto Module
import { Buffer } from 'node:buffer';
import fs from "node:fs";
import url from 'node:url';
import path from "node:path";
const {createCipheriv, createDecipheriv, randomFill, generateKey} = await import('node:crypto');
var claims = {name:"Joe",roles:["member","admin"]};
var keyEncryptionKey;
var ivKEK;
var contentEncryptionKey;
(async function () {
//pre shared secret as KEK
//Issuer randomly-generates a CEK.
keyEncryptionKey = await generateSecretKey ();
ivKEK = Buffer.from('A6A6A6A6A6A6A6A6','hex');
contentEncryptionKey = await generateSecretKey ();
let jwe = await returnJWE(claims);
console.log("\x1b[37mYour JWE token is: " + consoleString(jwe));
console.log("Paste JSON Web Token In Terminal Then Press Enter.")
})();
async function decryptJWE(jwe) {
try {
let parts = jwe.split(".");
let encodedJWEProtectedHeader = parts[0];
let protectedHeaderBuffer = Buffer.from(encodedJWEProtectedHeader,'base64url');
let encryptedKey = parts[1];
let iv = Buffer.from(parts[2],'base64url');
let cipherText = parts[3];
let tagBuffer = Buffer.from(parts[4],'base64url');
let decipherCEK = createDecipheriv('aes256-wrap', keyEncryptionKey, ivKEK);
let decryptedCEK = decipherCEK.update(encryptedKey,'base64url','base64url');
decryptedCEK += decipherCEK.final('base64url');
let decryptedCEKBuffer = Buffer.from(decryptedCEK,'base64url');
let decipher = createDecipheriv('aes-256-gcm', decryptedCEKBuffer, iv);
decipher.setAAD(protectedHeaderBuffer);
decipher.setAuthTag(tagBuffer);
let decrypted = decipher.update(cipherText,'base64url','utf8');
decrypted += decipher.final('utf8');
return JSON.parse(decrypted);
} catch (e) {
console.log("deciphering error is " + e)
return "\x1b[31mInvalid Token!\x1b[37m";
}
}
async function returnJWE(claimsObject){
let headerObject = { "alg": "A256KW", "enc": "A256GCM" };
let headerString = JSON.stringify(headerObject);
let ivCEK = await generateInitializationVector(12);
let claimsString = JSON.stringify(claimsObject);
let claimsBase64URLEncoded = Buffer.from(claimsString).toString('base64url');
//encrypt plaintext
let cipher = createCipheriv('aes-256-gcm', contentEncryptionKey, ivCEK);
cipher.setAAD(Buffer.from(headerString));
cipher.setAutoPadding();
let cipherText = cipher.update(claimsString,'utf8','base64url');
cipherText += cipher.final('base64url');
let tag = cipher.getAuthTag().toString('base64url');
let ivString = Buffer.from(ivCEK).toString('base64url');
let encodedJWEProtectedHeader = Buffer.from(headerString).toString('base64url');
//encrypt CEK
let cipherCEK = createCipheriv('aes256-wrap', keyEncryptionKey, ivKEK);
cipherCEK.setAutoPadding();
let exportedCEK = Buffer.from(contentEncryptionKey.export()).toString('base64url');
let encryptedCEK = cipherCEK.update(exportedCEK,'base64url','base64url');
encryptedCEK += cipherCEK.final('base64url');
let result = encodedJWEProtectedHeader + "." + encryptedCEK + "." + ivString + "." + cipherText + "." + tag;
return result;
}
function generateInitializationVector (n) {
return new Promise(function(resolve, reject) {
let buf = Buffer.alloc(n); //12 makes 96 bit
randomFill(buf, (err, buf) => {
if (err) reject (err);
resolve(buf);
});
});
}
function generateSecretKey () {
return new Promise(function(resolve, reject) {
generateKey('aes', { length: 256 }, (err, key) => {
if (err) {
reject (err);
}
resolve (key)
});
});
}
export default {decryptJWE, returnJWE};
process.stdin.setEncoding('utf8');
process.stdin.on('readable', async () => {
let chunk;
while ((chunk = process.stdin.read()) !== null) {
console.log(await decryptJWE(chunk));
}
});
function consoleString(token){
let tokenParts = token.split(/(\.)/);
let colors = ["32m","31m","33m","34m","36m"];
let color = "\x1b[X";
let str = ""
//str += color
tokenParts.forEach(function(part,index){
if(part != "."){
str += color.replace("X",colors.shift())
}
str += part;
str += "\x1b[37m"
})
return str;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment