One master key, derived into millions of card keys
"Generate IMK" creates the one long-term secret an issuer's HSM protects. Every individual card's ICC Master Key is derived from it on demand — the issuer never stores a separate key per card. This tool implements a simplified version of EMV's "Option A" derivation: encrypt the card's diversification data (PAN + PAN Sequence Number) under the IMK for one half of a double-length key, and encrypt its bitwise complement for the other half.
function deriveDoubleLengthKey(masterKeyHex, diversificationData16) {
const { k1, k2, k3 } = keyHalves(masterKeyHex);
const a = hexToBigInt(diversificationData16);
const b = a ^ ALL_F; // bitwise complement of the same 8 bytes
const halfA = tdesEncryptBlock(a, k1, k2, k3);
const halfB = tdesEncryptBlock(b, k1, k2, k3);
return bigIntToHex(halfA, 8) + bigIntToHex(halfB, 8);
}
const iccMasterKey = deriveDoubleLengthKey(imk, pan + psn);This is a real, simplified-for-teaching version of EMV's own Option A method — the complement trick is exactly how the spec gets two independent-looking half-keys out of one encryption key and one input block.
A fresh session key, every single transaction
"Card: derive keys & compute ARQC" reuses the exact same derivation function — a session key is derived from the ICC Master Key using the transaction's ATC as the diversification data, instead of the PAN and PSN. Because the ATC increments by one every transaction and never repeats for a given card, no two transactions ever share a session key.
const sessionKey = deriveDoubleLengthKey(iccMasterKey, atc.padStart(16, "0"));The one-function-two-purposes design mirrors something real EMV implementations do too: master-key diversification and session-key derivation are structurally the same operation, just applied at two different levels.
The ARQC: a MAC, not an encryption
The cryptogram itself is a single 3DES encryption of the transaction data under the session key — in this simplified demo, one 8-byte block; real EMV data typically spans more than one. The result isn't meant to be decrypted back into the transaction data; like any MAC, its only job is to be recomputable by someone holding the same key and the same data, and to come out completely different otherwise.
function mac(dataHex, keyHex) {
const { k1, k2, k3 } = keyHalves(keyHex);
return bigIntToHex(tdesEncryptBlock(hexToBigInt(dataHex), k1, k2, k3), 8);
}
const arqc = mac(amount + atc, sessionKey);No separate MAC algorithm — a single 3DES block encryption serves as the cryptogram directly, since there's only one input block in this simplified version.
Verification without a per-card key database
"Issuer: re-derive keys & recompute ARQC" deliberately never reads the card's ICC Master Key or session key from anywhere — it re-runs the exact same two derivations the card ran, starting from just the IMK, PAN, PSN, and ATC, all of which travel with the transaction or are already known to the issuer. If the recomputed ARQC matches, the issuer has simultaneously confirmed the card holds a key genuinely derived from its IMK and that this specific transaction's data wasn't altered in transit.
ARPC: the issuer's half of the proof
"Issuer: approve & compute ARPC" XORs the approval response code into the ARQC and encrypts that under the session key — Method 1 of EMV's ARPC computation. The card, which independently holds that same session key, runs the identical computation on its own copy of the ARQC and compares. An attacker sitting on the network and injecting a fake "approved" response has no way to produce a matching ARPC without the session key, which never travels anywhere outside the card and the issuer's HSM.
function computeArpc(arqcHex, responseCodeHex, sessionKeyHex) {
const { k1, k2, k3 } = keyHalves(sessionKeyHex);
const block = hexToBigInt(arqcHex) ^ hexToBigInt(responseCodeHex.padEnd(16, "0"));
return bigIntToHex(tdesEncryptBlock(block, k1, k2, k3), 8);
}
// both the issuer and the card run this exact function, independently, and compareBoth sides compute the same thing rather than one side decrypting the other's output — identical to how the ARQC itself works, and the same MAC-not-cipher pattern.