DESFire Key Diversification, Explained: SAM AV1's Legacy Method vs. AN10922 CMAC ("AV2")

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.

Master Key K (kept secret) Diversification function CBC-encrypt / CMAC UID₁ · AID · KeyNo UID₂ · AID · KeyNo Card 1 key unique to UID₁ Card 2 key unique to UID₂ Card N key unique to UIDâ‚™
Figure 1 — Same master key, different diversification input per card → an independent key per card. Breaking one diversified key does not expose the master key or any other card's 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

A note on sourcing: NXP never published this method as prose in a public application note — it lives in the MIFARE SAM AV1 functional specification, which is NDA-gated. What follows is reconstructed from NXP's own shipped, open-source reference implementation (the 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]
DivInput (IV) K block 1 E_K C1 K block 2 E_K C2 DivKey = C1 ‖ C2 (‖ C3 for 3-key TDEA / AES-192)
Figure 2 — Legacy (SAM AV1-style) diversification: the source key is CBC-encrypted under itself, with the diversification input standing in for the IV. Each output block feeds forward as the next block's IV, exactly like ordinary CBC.

Per key-type assembly

Key typeIV lengthConstruction
AES-12816 bytesOne AES block: DivKey = AES-CBC-Enc(K, IV, K)
AES-19216 bytesPlaintext is K zero-padded 24→32 bytes; encrypt two blocks, keep the first 24 bytes of output
2-key TDEA (16 B)8 bytesFull: 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 bytesThree 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 typeConstant(s)Max DivInputCMAC block size
AES-1280x0131 bytes16 bytes (AES)
AES-1920x11, 0x1231 bytes16 bytes (AES)
AES-256*0x41, 0x4231 bytes16 bytes (AES)
2-key TDEA0x21, 0x2215 bytes8 bytes (TDEA)
3-key TDEA0x31, 0x32, 0x3315 bytes8 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.

D = 0x01 DivInput (UID ‖ AID ‖ KeyNo…) 0x80 00…00 (if needed) E_K( block ⊕ 0 ) first blocks, IV = 0 chain last block ⊕ K1 or K2 E_K Diversified key Padded flag decides which subkey is used: • length(0x01 ‖ DivInput) is an exact multiple of the block size → no padding added → XOR last block with K1 • otherwise → append 0x80 then zero bytes to the boundary → XOR the padded last block with K2 AES-192 / 2TDEA / 3TDEA repeat this whole process once per constant (0x11+0x12, 0x21+0x22, 0x31+0x32+0x33) and concatenate the results.
Figure 3 — AN10922's CMAC-based construction. The constant byte fixes the key type and sub-block; standard CMAC subkey selection (K1/K2) and zero-IV CBC-MAC do the rest. This is exactly NIST SP 800-38B CMAC, just fed a purpose-built message.

Full formulas by key type

Key typeFormula
AES-128DivKey = AES128-CMAC(K, 0x01 ‖ DivInput) — 16 bytes out
AES-192A = 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 TDEADivKey = TDEA-CMAC(K, 0x21‖DivInput) ‖ TDEA-CMAC(K, 0x22‖DivInput) — 8+8 bytes
3-key TDEADivKey = 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.

Card-count limits are real. Because 2-key and 3-key TDEA CMAC is bounded by SP 800-38B's guidance on total encryptions under one key, AN10922 caps a single 2TDEA master key at roughly 500,000 diversified cards and a single 3TDEA master key at roughly 330,000 before the security margin erodes. AES-128 CMAC has no comparable practical ceiling for realistic fleet sizes — one more reason it's the default recommendation today.

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 K00112233445566778899AABBCCDDEEFF
UID04782E21801D80
AID3042F5
System identifier4E585020416275
DivInput (UID‖AID‖SysId)04782E21801D803042F54E585020416275
D = 0x01 ‖ DivInput0104782E21801D803042F54E585020416275 (18 bytes, needs padding)
Subkey K1 (derived from K)FBC9F75C9413C041DFEE452D3F0706D1
Subkey K2 (derived from K1)F793EEB928278083BFDC8A5A7E0E0D25
Padded last block ⊕ K2195E66EB928278083BFDC8A5A7E0E0D25
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")
PrimitiveRaw CBC self-encryptionStandardized CMAC (NIST SP 800-38B)
Public specificationNo — NDA functional spec onlyYes — AN10922, freely downloadable
Security proofNone published; relies on AES/TDEA's own strength as a block cipher used unusuallyInherits CMAC's formal one-wayness / PRF security proof
Diversification input lengthFixed at one cipher block (8 or 16 bytes) — no room for extra contextUp to 31 bytes (AES) / 15 bytes (TDEA) — UID + AID + key number + more all fit
AES-192 / larger keysSupported via block-chaining, undocumented publiclyExplicitly specified, two parallel CMAC passes combined
DES key-version handlingManual bit-level fix-up requiredSame fix-up still required for TDEA outputs — AES keys unaffected either way
Where it's used todayLegacy deployments; AV1-compatibility mode on some AV2 SAMsDefault 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.

Post a Comment

Previous Post Next Post