Every MIFARE DESFire deployment eventually runs into the same design question: if every card on site carries the same application key, then extracting that key from a single card — or from a reader, a badge cloner, or a leaked project file — compromises every card in the fleet at once. Key diversification is the standard answer. Instead of writing one shared key to every card, you derive a unique key per card from a secret master key plus something that's different on every card, usually the card's UID. Crack one diversified key and you've learned nothing about any other card's key or about the master key itself.
NXP has shipped two generations of this idea for DESFire ecosystems built around a Secure Access Module (SAM): an older, encryption-based method tied to the original SAM AV1 silicon, and a newer, CMAC-based method published openly in application note AN10922 and carried forward by SAM AV2 and SAM AV3. Integrators still run into both — AV1-style diversification because it's baked into older deployments and some AV2 SAMs run it in a compatibility mode, and AN10922/CMAC because it's the current recommendation for anything new. This post breaks down exactly how each one works at the byte level.
1. The core idea: one master key, many card keys
Both diversification families follow the same shape. You keep exactly one secret master key (sometimes called the base key) in your HSM or SAM. For every card, you feed that master key and a diversification input — the card's UID, plus usually the application ID and key number, so different applications/keys on the same card also diverge — through a one-way cryptographic function. Out comes a diversified key that is unique to that card, that application, and that key slot, and that reveals nothing about the master key or about any other card's diversified key.
Where the two families differ is entirely in what that function is. NXP shipped a working, encryption-based construction with the original SAM AV1 long before there was a public specification for it; AN10922 later replaced it with a construction built on the standardized AES/TDEA-CMAC primitive.
2. The legacy methodSAM AV1-style
phCryptoSym component of the NXP NFC Reader Library, function phCryptoSym_Sw_DiversifyDirectKey, mode PH_CRYPTOSYM_DIV_MODE_DESFIRE), not from a leaked spec. Treat the mechanism below as accurate to that code; a fully independent, NXP-published test vector for this specific method isn't public, so unlike the CMAC example further down, this one isn't numerically re-verified here.The legacy construction has no CMAC subkeys, no constants, and no explicit padding byte. It reuses ordinary CBC encryption in a clever way: it encrypts the key with itself, using the diversification input as the initialization vector.
DivKey = CBC-Encrypt( cipherKey = K, IV = DivInput, plaintext = K )
Unrolled one block at a time (⊕ is XOR, EK is single/triple-DES or AES encryption under K):
C1 = E_K( K_block1 ⊕ DivInput ) C2 = E_K( K_block2 ⊕ C1 ) C3 = E_K( K_block3 ⊕ C2 ) // only for 3-key TDEA / AES-192 DivKey = C1 ‖ C2 [‖ C3]
Per key-type assembly
| Key type | IV length | Construction |
|---|---|---|
| AES-128 | 16 bytes | One AES block: DivKey = AES-CBC-Enc(K, IV, K) |
| AES-192 | 16 bytes | Plaintext is K zero-padded 24→32 bytes; encrypt two blocks, keep the first 24 bytes of output |
| 2-key TDEA (16 B) | 8 bytes | Full: encrypt both 8-byte halves independently → C1‖C2. Half: encrypt only the first half and duplicate it → C1‖C1 (keeps a single-DES-equivalent key single-DES-equivalent) |
| 3-key TDEA (24 B) | 8 bytes | Three chained 8-byte blocks → C1‖C2‖C3 |
One detail that trips people up: DESFire stores a one-byte key version in the low bit (the DES parity bit position) of each byte of a DES/TDEA key's first 8-byte half. Because encryption scrambles those bits along with everything else, the legacy method explicitly reads the version out of the source key before diversifying, and re-writes those same version bits into the diversified key afterward — otherwise every diversified key would silently report version 0 regardless of what the master key's version actually was. AES keys store their version separately from the key bytes, so this fix-up step doesn't apply to them.
3. AN10922: the CMAC-based method"AV2 diversification"
AN10922 replaces ad-hoc self-encryption with a construction built directly on CMAC (NIST SP 800-38B) — the standardized, provably one-way message authentication mode already available on every AES and TDEA engine. Concretely, for a message D:
D = <constant> ‖ DivInput ‖ <padding if needed> DivKey = CMAC(K, D)
The constant byte (or bytes) is what keeps different key types — and different sub-blocks within AES-192/2TDEA/3TDEA — from ever colliding on the same CMAC input:
| Key type | Constant(s) | Max DivInput | CMAC block size |
|---|---|---|---|
| AES-128 | 0x01 | 31 bytes | 16 bytes (AES) |
| AES-192 | 0x11, 0x12 | 31 bytes | 16 bytes (AES) |
| AES-256* | 0x41, 0x42 | 31 bytes | 16 bytes (AES) |
| 2-key TDEA | 0x21, 0x22 | 15 bytes | 8 bytes (TDEA) |
| 3-key TDEA | 0x31, 0x32, 0x33 | 15 bytes | 8 bytes (TDEA) |
*AES-256 is not part of the published AN10922 worked examples; listed here for completeness where later application notes extend the scheme — verify against the current AN10922 revision for your SAM firmware before relying on it.
How CMAC itself works here
CMAC needs two 128-bit (or 64-bit, for TDEA) subkeys, K1 and K2, derived once from the master key:
L = E_K( 0-block ) K1 = L << 1, XOR 0x87 (AES) / 0x1B (TDEA) if L's top bit was 1 K2 = K1 << 1, XOR 0x87 / 0x1B if K1's top bit was 1
Then the message D is split into blocks. If D's length is not an exact multiple of the block size, it gets standard CMAC padding — a single 0x80 byte followed by zero bytes up to the block boundary — and the final block is XORed with K2 instead of K1. Every block (including the XORed last one) is then run through ordinary CBC-encryption with an all-zero IV; the diversified key is the final ciphertext block.
Full formulas by key type
| Key type | Formula |
|---|---|
| AES-128 | DivKey = AES128-CMAC(K, 0x01 ‖ DivInput) — 16 bytes out |
| AES-192 | A = AES192-CMAC(K, 0x11‖DivInput), B = AES192-CMAC(K, 0x12‖DivInput)DivKey = A[0:8] ‖ (A[8:16] ⊕ B[0:8]) ‖ B[8:16] |
| 2-key TDEA | DivKey = TDEA-CMAC(K, 0x21‖DivInput) ‖ TDEA-CMAC(K, 0x22‖DivInput) — 8+8 bytes |
| 3-key TDEA | DivKey = TDEA-CMAC(K,0x31‖DivInput) ‖ TDEA-CMAC(K,0x32‖DivInput) ‖ TDEA-CMAC(K,0x33‖DivInput) |
AN10922 also recommends what to put in DivInput: at minimum the card's UID, and for DESFire specifically the UID concatenated with the AID and key number so that different keys on the same card diverge independently — a compromised "read" key on a card tells an attacker nothing about that same card's "write" key.
4. A fully worked, independently-verified example
This is the AES-128 example from AN10922 itself. To make sure the description above is actually correct and not just a paraphrase, every intermediate value below was independently recomputed from scratch (standard AES-ECB + NIST SP 800-38B subkey derivation) rather than copied — and it matches the application note exactly.
| Master key K | 00112233445566778899AABBCCDDEEFF |
| UID | 04782E21801D80 |
| AID | 3042F5 |
| System identifier | 4E585020416275 |
| DivInput (UID‖AID‖SysId) | 04782E21801D803042F54E585020416275 |
| D = 0x01 ‖ DivInput | 0104782E21801D803042F54E585020416275 (18 bytes, needs padding) |
| Subkey K1 (derived from K) | FBC9F75C9413C041DFEE452D3F0706D1 |
| Subkey K2 (derived from K1) | F793EEB928278083BFDC8A5A7E0E0D25 |
| Padded last block ⊕ K2 | 195E66EB928278083BFDC8A5A7E0E0D25 |
| Diversified key (AES-128) | A8DD63A3B89D54B37CA802473FDA9175 |
Two things worth noticing here. First, an 18-byte DivInput is not a multiple of the 16-byte AES block once you prepend the 1-byte constant (19 bytes total), so the message gets padded and K2 is the subkey in play — if the UID/AID/SysId combination had happened to land on exactly 15 or 31 bytes of DivInput, K1 would be used instead with no padding at all. Second, the entire master key K never leaves the SAM/HSM in this process — only DivInput (which is not secret; it's derived from data already on the card) needs to reach whatever's requesting the diversified key.
5. Legacy vs. CMAC: side-by-side
| Legacy (SAM AV1-style) | AN10922 (CMAC / "AV2") | |
|---|---|---|
| Primitive | Raw CBC self-encryption | Standardized CMAC (NIST SP 800-38B) |
| Public specification | No — NDA functional spec only | Yes — AN10922, freely downloadable |
| Security proof | None published; relies on AES/TDEA's own strength as a block cipher used unusually | Inherits CMAC's formal one-wayness / PRF security proof |
| Diversification input length | Fixed at one cipher block (8 or 16 bytes) — no room for extra context | Up to 31 bytes (AES) / 15 bytes (TDEA) — UID + AID + key number + more all fit |
| AES-192 / larger keys | Supported via block-chaining, undocumented publicly | Explicitly specified, two parallel CMAC passes combined |
| DES key-version handling | Manual bit-level fix-up required | Same fix-up still required for TDEA outputs — AES keys unaffected either way |
| Where it's used today | Legacy deployments; AV1-compatibility mode on some AV2 SAMs | Default for new SAM AV2/AV3 deployments |
6. Which one should you actually use
If you're standing up a new deployment, there generally isn't a decision to make: use AES-128 with AN10922 CMAC diversification. It's openly documented (so you're never debugging a black box), it has no practical card-count ceiling, and it gives you room in DivInput to bind the key to the UID, the AID, and the key number in one pass. The legacy method only really shows up when you're maintaining or migrating an existing DES/2TDEA-based system that was provisioned before AN10922 existed, or when a specific SAM is running in AV1-compatibility mode for interoperability with an older host application — and even then, TDEA's ~500k/~330k card ceilings are a real planning constraint worth checking against your fleet size before you commit a master key to it.
References: NXP AN10922 — Symmetric key diversifications; NXP AN12696 — MIFARE SAM AV3 for MIFARE DESFire; NXP P5DF081 — MIFARE SAM AV2 short data sheet. Legacy-method mechanics reconstructed from NXP's own open-source NFC Reader Library (phCryptoSym_Sw_DiversifyDirectKey); no NXP-published test vector exists publicly for that specific path, so unlike the CMAC example above it is not independently numerically re-verified in this post.