Created
August 29, 2023 21:35
-
-
Save ronnievsmith/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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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