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 )
Test this yourself: Our Key Diversification Calculator performs card-level + session-level derivation. The 3DES/AES Session Key Visualizer shows EMV ARQC key derivation step by step.

5. EMV Issuer Key Hierarchy

KeyDerived FromPurposeLength
IMK (Issuer Master Key)HSM (root)Top-level issuer secret in HSM16 bytes (3DES) or 32 bytes (AES)
MK (ICC Master Key)IMK + PAN + PAN SeqCard-specific master key16 bytes
SK (Session Key)MK + ATCDerive ARQC cryptogram16 bytes
CSK (Common Session Key)MK + ATC via Option A/BSame as SK, EMV CSK spec name16 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