All Tools
Tools/UDK Calculator
🔑
Cryptography & KeysAvailable

UDK Calculator

Derive EMV Unique Derived Keys (UDK) from an Issuer Master Key (IMK) and PAN/PAN Sequence Number — for EMV card personalisation and transaction key derivation.

Follows EMV 4.4 Book 2 Annex A1.4.1Option A (PAN up to 16 digits: PAN ∥ PSN, rightmost 16 digits); longer PANs rejected, Option B not implemented·verified Oct 2026· double-checked Oct 2026— cross-checked against OpenSSL TDES

Try it as a puzzle: deliver a chip’s cryptogram to the one system that can check it →

Use test data only. All calculations run locally in your browser — PayProbe never sees, transmits, or stores your PAN, CVV, keys, PINs, or cryptographic inputs. How we handle data →

Algorithm (EMV Book 2 A1.4.1, Option A)

  1. Y = the rightmost 16 digits of PAN ∥ PAN Sequence Number (PSN 00 if absent; left-padded with zeros if shorter)
  2. Left half = 2TDEA encryption of Y under the IMK
  3. Right half = 2TDEA encryption of (Y XOR FFFFFFFFFFFFFFFF) under the IMK
  4. UDK = left ∥ right, with DES odd parity set on each byte
  5. Option B, required for PANs longer than 16 digits, hashes PAN ∥ PSN with SHA-1 first; this tool implements Option A only and reports an error for longer PANs
  6. The IMK must be a double-length (32 hex) TDES key
</> Code Implementation
# EMV ICC master key (UDK) derivation — EMV Book 2 A1.4.1, Option A
# Requires: pip install pycryptodome
from Crypto.Cipher import DES3

def adjust_parity(b: int) -> int:
    # Force DES odd parity on a byte
    return b ^ 1 if bin(b).count("1") % 2 == 0 else b

def des3_encrypt(key: bytes, data: bytes) -> bytes:
    return DES3.new(key[:16] + key[:8], DES3.MODE_ECB).encrypt(data)

def derive_udk(imk_hex: str, pan: str, pan_seq: str = "00") -> str:
    imk = bytes.fromhex(imk_hex)
    # Option A (PAN up to 16 digits); the PSN is always two digits ("1" -> "01").
    y = bytes.fromhex((pan + pan_seq.zfill(2))[-16:].zfill(16))
    left  = des3_encrypt(imk, y)
    right = des3_encrypt(imk, bytes(b ^ 0xFF for b in y))
    return bytes(adjust_parity(b) for b in left + right).hex().upper()

print(derive_udk("0123456789ABCDEFFEDCBA9876543210", "4111111111111111", "01"))
# A768CE9E1551FDE3A45B4C7C3DE9DC52
Something wrong or missing? Report a problem— we check reports against the specs and fix them.