How to Derive Smart Card Keys — Diversification, Session Keys & Secure Channels
Smart card security depends on never exposing the master key. Instead, every card and every session gets derived keys. This guide covers the three key derivation layers: card-level diversification (Master → Card-specific key), session key derivation (Card key → Session keys for SCP02/SCP03 secure channels), and EMV issuer key trees (IMK → MK → SK + CSK for transaction cryptograms).
1. Card Key Diversification from Master Key
Given a master key (MK) and a card-unique serial number (UID/CSN), derive a card-specific key so each card has a unique key set:
NXP/MIFARE Diversification (proprietary)
# Input: 16-byte Master Key, 7-byte UID
# Diversify using AES-128 or 3DES on UID
from Crypto.Cipher import AES
def nxp_diversify(master_key, uid):
"""NXP AN10922 diversification."""
# Build diversification input: UID padded/processed
div_input = uid + bytes([0x00]) # padding varies by chip
# AES encrypt with master key
cipher = AES.new(master_key, AES.MODE_ECB)
diversified = cipher.encrypt(div_input[:16])
return diversified # 16-byte card-specific key
EMV Diversification (Issuer Master Key → ICC Master Key)
# IMK → MK: encrypt PAN || PAN Sequence Number with IMK
# PAN = Primary Account Number (right-justified, padded to 8 bytes)
# MK = 3DES_IMK( PAN_padded || PAN_Seq )
from Crypto.Cipher import DES3
def emv_derive_mk(imk, pan, pan_seq=0):
"""Derive ICC Master Key from Issuer Master Key."""
pan_str = str(pan).zfill(16)[-16:] # Right-justify to 16 digits
input_data = bytes.fromhex(pan_str) + bytes([pan_seq])
# Pad to 16 bytes
input_data = input_data.ljust(16, b'\x00')
cipher = DES3.new(imk, DES3.MODE_ECB)
mk = cipher.encrypt(input_data[:8]) # First 8 bytes of result
return mk
2. GlobalPlatform SCP02 Session Keys
After OPEN, the secure channel session uses three session keys derived from the card key + host challenge + card challenge:
# SCP02 derives 3 session keys from CMK:
# S-ENC: Session Encryption Key (for APDU payload)
# S-MAC: Session MAC Key (for command authentication)
# S-DEK: Session Data Encryption Key (or S-RMAC for response MAC)
# Derivation constants (GP 2.2.1):
ENC_CONSTANT = b'\x01\x82' # 0x0182 for S-ENC
MAC_CONSTANT = b'\x01\x01' # 0x0101 for S-MAC
DEK_CONSTANT = b'\x01\x81' # 0x0181 for S-DEK (DEK in older specs)
def scp02_derive_session_key(cmk, constant, derivation_data):
"""Derive a single SCP02 session key using 3DES Retail MAC."""
# derivation_data = host_challenge[0:4] + card_challenge[0:4]
# constant = ENC_CONSTANT, MAC_CONSTANT, or DEK_CONSTANT
pad_constant = constant + b'\x00' * (8 - len(constant))
# Step 1: 3DES_CBC with IV=0 on pad_constant + derivation_data
# ... full derivation uses 3DES CBC-MAC
3. GlobalPlatform SCP03 Session Keys
SCP03 uses AES-128 with a more robust derivation using a 12-byte context (DD) and L=0x0040 (AES key length):
# SCP03 key derivation (NIST SP 800-108 KDF in Counter mode):
# KDF(K, label, context, L) = PRF(K, i || label || 0x00 || context || L)
# For S-ENC: label = 0x00, context = host_challenge + card_challenge
# For S-MAC: label = 0x01
# For S-RMAC: label = 0x02 (response MAC)
from Crypto.Hash import CMAC
from Crypto.Cipher import AES
def scp03_kdf(key, constant, context):
"""SCP03 KDF using AES-CMAC-128."""
label = bytes([constant]) + b'\x00' * 11 # 12-byte label
data = bytes([0x01]) + label + bytes([0x00]) + context + bytes([0x00, 0x40])
mac = CMAC.new(key, ciphermod=AES)
mac.update(data)
return mac.digest() # 16-byte derived key
4. EMV Session Key for Cryptograms
EMV cards use a session key (SK) derived from the ICC Master Key (MK) + ATC (Application Transaction Counter) to generate ARQC:
# EMV CSK Option A (common for Visa/MC):
# SK = 3DES_MK( ATC || F8 || 00...00 )
# MK is the ICC Master Key (16 bytes for 3DES)
# ATC is 2 bytes, zero-padded to 8 bytes
def emv_derive_sk_acs(mk, atc):
"""EMV Common Session Key (CSK) — Option A."""
# Build input: (ATC || 0xF800...)
input_data = bytes([atc >> 8, atc & 0xFF]) # 2 bytes ATC
input_data += bytes([0xF8, 0x00, 0x00, 0x00, 0x00, 0x00]) # Pad
cipher = DES3.new(mk, DES3.MODE_ECB)
sk = cipher.encrypt(input_data)
return sk # 8 bytes (2TDEA) or 16 bytes (3TDEA)
# For AES-based EMV kernels:
# SK = AES_MK( ATC_padded )
5. EMV Issuer Key Hierarchy
| Key | Derived From | Purpose | Length |
|---|---|---|---|
| IMK (Issuer Master Key) | HSM (root) | Top-level issuer secret in HSM | 16 bytes (3DES) or 32 bytes (AES) |
| MK (ICC Master Key) | IMK + PAN + PAN Seq | Card-specific master key | 16 bytes |
| SK (Session Key) | MK + ATC | Derive ARQC cryptogram | 16 bytes |
| CSK (Common Session Key) | MK + ATC via Option A/B | Same as SK, EMV CSK spec name | 16 bytes |
Related Tools
Key Diversification Calculator — Card + session key derivation | Session Key Visualizer — EMV/MIFARE session key generation | EMV Cryptogram Visualizer — ARQC from session key | GP SCP02 vs SCP03 Guide