← Back to the tool

Payments · Under the hood

EMV ARQC/ARPC simulator

What each button in the tool actually computes, step by step — with the real formulas.

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.

Deriving a double-length key from one master key
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.

Same function, different diversification input
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.

The cryptogram computation itself
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.

ARPC Method 1: XOR the response code in, then encrypt once
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 compare

Both 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.

Now try it

Back to EMV ARQC/ARPC simulator →

Play both sides of a chip transaction: derive a card's keys from an issuer master key, compute a real ARQC cryptogram, then watch the issuer independently re-derive everything and answer with an ARPC the card verifies on its own.