
Part 2 of a series on how chip cards are made. Part 1 covered what personalisation is and who does it.
A blank chip has a trust problem.
In a few seconds it will receive the most sensitive things it will ever hold: the keys that prove it's genuine on every payment, and your PIN. They'll come from a machine it has never met, one of thousands in card bureaus and bank branches around the world. Nobody is standing there to vouch for it.
So before it accepts anything, the chip makes the machine prove itself, and the machine makes the chip prove itself too. The EMV Card Personalisation Specification (CPS) calls this setting up a secure channel. I think of it as a handshake, and it's one of the neater bits of design in the whole card system.
The chip already has keys when it leaves the factory
This surprised me when I first read it. A "blank" chip isn't completely blank. Before it's ever sent to a bureau, the card maker loads it with three keys of its own. CPS names them KENC, KMAC and KDEK, and you'll see why there are three in a moment.
Those keys are different on every chip, but nobody keeps a list of them. They're worked out from one master key, which CPS calls the KMC, mixed with data unique to the chip, including its serial number. The card maker and the bureau both hold the master key, locked inside hardware security modules.
That means a bureau doesn't need a database of millions of card keys. When a card arrives at the machine, it asks the chip for its serial number and recomputes that chip's keys on the spot from the master key. The keys exist in two places only: inside the chip, and for a moment inside the bureau's security module.

Step one: "Here's a challenge"
The machine starts by sending the chip a command called INITIALIZE UPDATE, carrying 8 random bytes. That's its challenge.
The chip answers with a handful of things:
- which master key it was loaded under (an identifier, often built from the bank's card number range, plus a version number),
- the data it uses to derive its keys, including that serial number,
- a counter that goes up by one every time a handshake succeeds,
- its own challenge for the machine,
- and a short code called the card cryptogram.
The card cryptogram is the chip's proof. It's calculated from both challenges and the counter, using a key only the real chip should have. Anyone can send back numbers. Only the right chip can send back the right card cryptogram.
Step two: the machine checks the chip
Now the machine has what it needs. It takes the master key identifier, finds that master key, and derives this chip's three keys from the serial number. Then it uses the counter to turn them into three session keys, used for this one session and never again.
With those, it calculates what the card cryptogram should be and compares it with what the chip sent. If they don't match, the machine stops. Either this isn't a genuine chip, or it was loaded under a different master key than the bureau expects. Either way, nothing gets written.
Step three: the chip checks the machine
The machine then sends EXTERNAL AUTHENTICATE, carrying its own proof, the host cryptogram, calculated over the challenges and the counter. Now it's the chip's turn to compare. If the machine got it wrong, the chip ends the session and refuses everything that follows.
The same command tells the chip how careful to be for the rest of the session. CPS gives three levels: no protection, a tamper seal (a MAC) on every command, or a tamper seal plus encryption of the whole command. For a production card, you want one of the protected levels.
When the chip accepts, the counter goes up. The next handshake, even with the same machine, will produce different session keys. A recording of today's session is useless tomorrow.

Three session keys, three jobs
This is why the chip has three keys and not one. Each session key does a single job:
- one for proving identity in the handshake and encrypting command data (CPS calls it SKUENC),
- one for the tamper seals on every command (SKUMAC),
- one for wrapping the most sensitive things, the card's keys and the PIN reference (SKUDEK).
Keeping the jobs apart limits the damage if one key is ever exposed, and it's standard practice in good cryptographic design.
How your PIN gets to the chip without anyone seeing it
Here's the journey of a secret, like the PIN reference or the card's payment keys, from the bank to the chip.
At data preparation, the secret is encrypted under a transport key shared between the data preparation system and the bureau. It travels to the bureau in that form.
At the bureau, inside the security module, it's decrypted and immediately re-encrypted under this card's SKUDEK. Then it's sent to the chip, which is the only thing that can open it.
CPS describes this as two key zones: one between the bank's data preparation and the bureau, and one between the bureau and the chip. The data preparation system doesn't need to know anything about the chip, and the chip doesn't need to know anything about the bank's systems. Each side only trusts its neighbour.
Where this goes wrong
Some thoughts of my own, from looking at a lot of personalisation data.
The good news is that problems in the handshake are loud. If a batch of chips was loaded under a different master key than the bureau is using, the card cryptogram won't match and the machine rejects the card. Nobody ships a card that failed the handshake. Compare that with the quiet risk-setting mistakes from part 1, which can sail through to customers.
The thing worth checking is which master key a production batch actually uses. There are well-known test master keys printed in public documentation and used in labs everywhere. They're fine for development. A production run should never be personalised under one, and the master key identifier and version a card reports are among the first things worth looking at when you check a perso log.
One more change is under way. The 2021 edition of CPS adds AES, a newer cipher, alongside the triple DES the original handshake uses. The shape of the handshake stays the same, with challenges, cryptograms, a counter and session keys, but bureaus and data preparation systems have to support both.
For the technical reader
If you'd like to see the handshake in numbers, here are the formulas from CPS (the original triple-DES version; CPS 2.0's AES version keeps the same shape). || means "joined together".
Session keys (CPS 5.3, Table 24)
SKUENC = 3DES-CBC(KENC, '0182' || counter || 12 zero bytes)
SKUMAC = 3DES-CBC(KMAC, '0101' || counter || 12 zero bytes)
SKUDEK = 3DES-CBC(KDEK, '0181' || counter || 12 zero bytes)
Card cryptogram (CPS 3.2.5.10)
MAC(SKUENC, host challenge || counter || card challenge)
Host cryptogram (CPS 3.2.6.6)
MAC(SKUENC, counter || card challenge || host challenge)
The MAC is a full triple-DES CBC MAC: pad the data with 80 and then zeros to a multiple of 8 bytes, encrypt in CBC mode with a zero starting block, and keep the last 8 bytes (CPS 5.4.1, ISO/IEC 9797-1 MAC algorithm 1). Notice that both cryptograms cover the same three values in a different order, so neither side can pass the test by echoing the other's proof.
The code below computes a session key and both cryptograms. It uses the public test key 404142…4F and made-up challenges, so you can run it safely and compare your own implementation against it.
Python
# pip install pycryptodome
from Crypto.Cipher import DES3
def tdes_cbc(key, data):
"""Triple DES in CBC mode with a zero IV and no padding (CPS 5.5.2)."""
return DES3.new(key, DES3.MODE_CBC, iv=bytes(8)).encrypt(data)
def session_key(card_key, label, counter):
"""CPS 5.3, Table 24: label + counter + 12 zero bytes, encrypted with the card key."""
return tdes_cbc(card_key, label + counter + bytes(12))
def mac(key, data):
"""Full triple-DES MAC (CPS 5.4.1): pad with 80 then zeros, CBC, keep the last 8 bytes."""
padded = data + b"\x80" + bytes(7 - len(data) % 8)
return tdes_cbc(key, padded)[-8:]
k_enc = bytes.fromhex("404142434445464748494A4B4C4D4E4F") # public TEST key, never use in production
counter = bytes.fromhex("002A") # from the chip's INITIALIZE UPDATE reply
host_challenge = bytes.fromhex("1122334455667788") # the machine's 8 random bytes
card_challenge = bytes.fromhex("A1B2C3D4E5F6") # the chip's 6-byte challenge
sku_enc = session_key(k_enc, bytes.fromhex("0182"), counter)
card_cryptogram = mac(sku_enc, host_challenge + counter + card_challenge) # CPS 3.2.5.10
host_cryptogram = mac(sku_enc, counter + card_challenge + host_challenge) # CPS 3.2.6.6
print("SKUENC ", sku_enc.hex().upper())
print("card cryptogram ", card_cryptogram.hex().upper())
print("host cryptogram ", host_cryptogram.hex().upper())
C#
using System.Security.Cryptography;
// Triple DES in CBC mode with a zero IV and no padding (CPS 5.5.2).
static byte[] TdesCbc(byte[] key, byte[] data)
{
using var tdes = TripleDES.Create();
// A double-length key K1K2 is the same cipher as K1K2K1; some platforms only accept 24 bytes.
tdes.Key = key.Length == 16 ? [.. key, .. key[..8]] : key;
return tdes.EncryptCbc(data, new byte[8], PaddingMode.None);
}
// CPS 5.3, Table 24: label + counter + 12 zero bytes, encrypted with the card key.
static byte[] SessionKey(byte[] cardKey, byte[] label, byte[] counter) =>
TdesCbc(cardKey, [.. label, .. counter, .. new byte[12]]);
// Full triple-DES MAC (CPS 5.4.1): pad with 80 then zeros, CBC, keep the last 8 bytes.
static byte[] Mac(byte[] key, byte[] data) =>
TdesCbc(key, [.. data, 0x80, .. new byte[7 - data.Length % 8]])[^8..];
byte[] kEnc = Convert.FromHexString("404142434445464748494A4B4C4D4E4F"); // public TEST key, never use in production
byte[] counter = Convert.FromHexString("002A"); // from the chip's INITIALIZE UPDATE reply
byte[] hostChallenge = Convert.FromHexString("1122334455667788"); // the machine's 8 random bytes
byte[] cardChallenge = Convert.FromHexString("A1B2C3D4E5F6"); // the chip's 6-byte challenge
byte[] skuEnc = SessionKey(kEnc, [0x01, 0x82], counter);
byte[] cardCryptogram = Mac(skuEnc, [.. hostChallenge, .. counter, .. cardChallenge]); // CPS 3.2.5.10
byte[] hostCryptogram = Mac(skuEnc, [.. counter, .. cardChallenge, .. hostChallenge]); // CPS 3.2.6.6
Console.WriteLine($"SKUENC {Convert.ToHexString(skuEnc)}");
Console.WriteLine($"card cryptogram {Convert.ToHexString(cardCryptogram)}");
Console.WriteLine($"host cryptogram {Convert.ToHexString(hostCryptogram)}");
Both print:
SKUENC 7AA8DE1A36F4F51AFBC7E1579F778B44
card cryptogram A05C5918994FD9BD
host cryptogram B2DE615049B9DAD0
Change the counter from 002A to 002B and every value changes. That's the "recording of today's session is useless tomorrow" property, in four characters.
Next in the series
So far we've followed the chip through the factory and the bureau. In part 3 I'll move to the shop counter: how a terminal decides a card is genuine without calling anyone, using the certificates written onto the chip during personalisation.
If you work in a bureau or on data preparation, I'd like to know whether the "wrong key in production" problem is something you've actually run into. Tell me in the comments.

