const crypto = require("crypto");
// Warning: keydatalen is in bytes, not in bits!
function concatKDFSHA256(z, keydatalen, otherInfo) {
const reps = Math.ceil(keydatalen / 32); // 32 bytes in the size oh SHA256's output
let counter = 1;
const Ks = [];
for (let i = 0; i < reps; i++) {
const counterAsUInt32BE = Buffer.alloc(4);
counterAsUInt32BE.writeUInt32BE(counter);
const dataToHash = Buffer.concat([counterAsUInt32BE, z, otherInfo]);
const Ki = crypto.createHash("sha256").update(dataToHash).digest();
Ks.push(Ki);
counter++;
}
const remainingBytes = keydatalen % 32;
if (remainingBytes !== 0) {
Ks[reps - 1] = Ks[reps - 1].slice(0, remainingBytes);
}
return Buffer.concat(Ks).slice(0, keydatalen);
}
const jwe = "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6IkFTRHNMLUp4MlhPa1JuRnRxVy1RYmxXWS1tRG5RVzJMZ2FwYWRGeDc1dEEiLCJlcGsiOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJoWGpLdmNWa3d2ZHR4M19sdnJqTmJtQkZfZW9BZjNMeGJCblBNVFJxY2JvIiwieSI6IlJHOEN5WDV5bW9oTGV0cTFhVzgzVG96UU1EaEdQbTNHNXlwZERmVy1mSlUifX0..6L__CO5yA3g4tkLY.HysY-036kmszNeXDdgP375x0NfFlobYYGoL51A5PQ4Js9y287qZZ.QFYcZCfC6JAo1-snZ6O5Cw";
const privateKey = "-----BEGIN EC PRIVATE KEY-----\r\nMHcCAQEEIFUvax1cOkuRkKFmypV7FtN5GR+1PjSXJ3yS0yGcn0R7oAoGCCqGSM49\r\nAwEHoUQDQgAEUbInEqNbZZZ9SJptBwKTKO6qslSyuWvMkVK44Bx/d8U9TF4cw1Uv\r\nRVHE5gmuQdyl7qxqnsgb4Ct7RBuitkOeAg==\r\n-----END EC PRIVATE KEY-----";
const [jweHeader, jweEncryptedKey, jweIv, jweCiphertext, jweAuthenticationTag] = jwe.split(".");
const decodedHeader = JSON.parse(Buffer.from(jweHeader, "base64").toString("utf8"));
console.log("JWE header:", JSON.stringify(decodedHeader));
const sharedSecret = crypto.diffieHellman({
privateKey: crypto.createPrivateKey(privateKey),
publicKey: crypto.createPublicKey({
key: decodedHeader.epk,
format: "jwk"
})
})
const algorithmIdData = Buffer.from(decodedHeader.enc, "ascii");
const algorithmIdDataLength = Buffer.alloc(4);
algorithmIdDataLength.writeUInt32BE(algorithmIdData.length);
const algorithmId = Buffer.concat([algorithmIdDataLength, algorithmIdData]);
const partyUInfo = Buffer.from([0, 0, 0, 0]);
const partyVInfo = Buffer.from([0, 0, 0, 0]);
const suppPubInfo = Buffer.alloc(4);
suppPubInfo.writeUInt32BE(256); // 256 = keydatalen in bits
const otherInfo = Buffer.concat([algorithmId, partyUInfo, partyVInfo, suppPubInfo]);
const derivedKey = concatKDFSHA256(sharedSecret, 32, otherInfo);
const iv = Buffer.from(jweIv, "base64");
const ciphertext = Buffer.from(jweCiphertext, "base64");
const authTag = Buffer.from(jweAuthenticationTag, "base64");
const decipher = crypto.createDecipheriv("aes-256-gcm", derivedKey, iv);
const clearText = decipher.update(ciphertext).toString();
console.log("Clear text:", clearText);
// Output:
// JWE header: {"alg":"ECDH-ES","enc":"A256GCM","kid":"ASDsL-Jx2XOkRnFtqW-QblWY-mDnQW2LgapadFx75tA","epk":{"kty":"EC","crv":"P-256","x":"hXjKvcVkwvdtx3_lvrjNbmBF_eoAf3LxbBnPMTRqcbo","y":"RG8CyX5ymohLetq1aW83TozQMDhGPm3G5ypdDfW-fJU"}}
// Clear text: {"pan":"1234567891234567","exp":"1223"}