Project 3: Point-to-Point Encryption (P2PE) Simulator
Goal: Build practical expertise in payment security by implementing core controls (validation, tokenization, encryption), understanding PCI scope, and producing auditable, compliant artifacts.
Payment Data Boundaries
Payment systems live or die by data boundaries: where PANs can exist, how they move, and who can touch them. You need to draw a clear boundary between the Cardholder Data Environment (CDE) and everything else to reduce scope and risk.
Cryptographic Controls and Key Management
Payments rely on strong symmetric encryption, deterministic tokenization, and strict key lifecycle controls. Key custody, rotation, and HSM-backed operations are as important as the algorithms themselves.
Transaction Flow and Compliance Guarantees
Authorization, capture, and settlement have different security requirements. Compliance (PCI DSS, PCI PIN, 3DS) enforces minimal guarantees that must be reflected in system design.
Concept Summary Table
| Concept Cluster | What You Need to Internalize |
|---|---|
| Data classification | PAN vs token, CDE boundaries, data minimization. |
| Cryptography | AES, KDFs, tokenization, key hierarchy. |
| Transaction security | Auth vs settlement, 3DS, P2PE. |
| Compliance | PCI DSS scope, audit controls, evidence. |
| Risk controls | Rate limits, fraud signals, logging. |
Deep Dive Reading by Concept
| Concept | Book & Chapter |
|---|---|
| PCI DSS | PCI DSS v4.0 — Requirements overview |
| Tokenization | PCI Tokenization Guidelines — Implementation sections |
| Crypto in payments | Cryptography Engineering — Ch. 6-9 |
| Payment flows | Payment Systems in the U.S. — transaction chapters |
| Fraud controls | The Anatomy of the Payment Card Industry — risk sections |
Project Overview
| Attribute | Value |
|---|---|
| Difficulty | Level 4: Expert |
| Time Estimate | 2-3 weeks |
| Programming Language | C |
| Knowledge Area | Payment Security / Key Management |
| Key Technologies | DUKPT, HSM Simulation, 3DES/AES |
| Coolness Level | Level 4: Hardcore Tech Flex |
| Business Potential | 3. The “Service & Support” Model |
Learning Objectives
By completing this project, you will:
- Understand P2PE architecture - Learn why encryption “at the point of swipe” is the gold standard
- Implement DUKPT key derivation - Master the Derived Unique Key Per Transaction algorithm
- Simulate HSM boundaries - Understand how trust boundaries work in payment systems
- Handle key injection ceremonies - Learn how devices get their initial keys
- Manage transaction counters - Understand key exhaustion and device lifecycle
- Apply defense-in-depth principles - See why multiple security layers matter
The Core Question You’re Answering
“How can card data travel through merchant systems without ever being decryptable by the merchant?”
This is the fundamental problem P2PE solves:
┌─────────────────────────────────────────────────────────────────────────┐
│ THE P2PE SECURITY MODEL │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ WHAT THE MERCHANT SEES: │
│ ═══════════════════════ │
│ │
│ Customer swipes card │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Card Reader │ │
│ │ (P2PE Device) │ │
│ │ │ │
│ │ PAN: 4532...0366 │ ← Encrypted inside the reader │
│ │ ════════════════ │ │
│ │ Output: │ │
│ │ "A7F3B2C1D4E5..." │ ← Merchant only sees ciphertext │
│ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Merchant POS │ Passes encrypted blob │
│ │ System │ ────────────────► │
│ │ │ CANNOT decrypt │
│ │ Key? ❌ None! │ (no key access) │
│ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Payment │ │
│ │ Processor HSM │ │
│ │ │ │
│ │ Key: ✓ Has DEK │ ← ONLY entity that can decrypt │
│ │ Decrypts inside │ │
│ │ HSM boundary │ │
│ └─────────────────────┘ │
│ │
│ RESULT: Even if merchant is completely compromised, │
│ attacker cannot recover card numbers from transaction logs │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Why this is the “gold standard”:
- Merchant systems NEVER touch plaintext card data
- Even with full network access, attackers see only ciphertext
- Unique key per transaction prevents mass compromise
- HSM boundary ensures keys never exist in regular memory
Deep Theoretical Foundation
1. What is P2PE?
Point-to-Point Encryption encrypts card data at the moment of capture (the “point” of swipe/dip/tap) and keeps it encrypted until it reaches a secure decryption environment (the processor’s HSM).
PCI P2PE Standard:
- Defined by PCI Security Standards Council
- Requires validated P2PE solutions
- Dramatically reduces merchant PCI scope
The Security Chain:
┌─────────────────────────────────────────────────────────────────────────┐
│ P2PE SECURITY CHAIN │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Card │ │ Terminal │ │ Merchant │ │Processor │ │
│ │ │───►│ (P2PE) │───►│ Network │───►│ HSM │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Plaintext│ │ Encrypted │ │ Encrypted │ │ Plaintext │ │
│ │ PAN │ │ (DUKPT key) │ │ (passing │ │ (inside HSM │ │
│ │ │ │ │ │ through) │ │ only) │ │
│ └──────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ Trust Boundary: Card → Terminal (data enters protected channel) │
│ Trust Boundary: Processor HSM (data exits protected channel) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
2. DUKPT: Derived Unique Key Per Transaction
DUKPT is THE algorithm that makes P2PE work. It generates a unique encryption key for every transaction from a base key.
The Problem DUKPT Solves:
- If every terminal uses the same key → compromise one, compromise all
- If terminals store individual keys → key distribution nightmare
- If terminals derive keys → need secure derivation mechanism
DUKPT Solution: Inject a “Base Derivation Key” (BDK) once. Terminal derives unique keys forever.
┌─────────────────────────────────────────────────────────────────────────┐
│ DUKPT KEY HIERARCHY │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ │
│ │ Base Derivation Key │ Known only to HSM │
│ │ (BDK) │ 128 bits (2-key 3DES) │
│ │ │ or 256 bits (AES) │
│ └──────────┬──────────┘ │
│ │ │
│ Key Derivation │ One-way function │
│ KSN[0:9] XOR mask │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Initial PIN │ Unique per terminal │
│ │ Encryption Key │ Injected into device │
│ │ (IPEK) │ │
│ └──────────┬──────────┘ │
│ │ │
│ Future Key Tree │ 21 "future keys" derived │
│ (pre-computed) │ from counter bits │
│ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │ FK[1] │ │ FK[2] │ │ FK[3] │ ... │FK[21] │ │
│ └───┬───┘ └───┬───┘ └───┬───┘ └───┬───┘ │
│ │ │ │ │ │
│ └──────────────┴──────────────┴──────────────┘ │
│ │ │
│ Transaction Key │ Derived from appropriate │
│ Derivation │ future key based on counter │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Transaction Key │ Unique per transaction │
│ │ (PIN or Data) │ Used once, then forgotten │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Key Serial Number (KSN):
The KSN is a 10-byte identifier that tracks which key to use:
┌─────────────────────────────────────────────────────────────────────────┐
│ KEY SERIAL NUMBER (KSN) FORMAT │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┬─────────────────────┬────────────────────┐ │
│ │ BDK ID (5B) │ Device ID (4B) │ Counter (21 bits) │ │
│ │ │ │ │ │
│ │ Identifies │ Identifies │ Tracks which │ │
│ │ which BDK │ which terminal │ transaction │ │
│ │ was used │ │ (max 1M) │ │
│ └──────────────────┴─────────────────────┴────────────────────┘ │
│ │
│ Example: FFFF 9876543210 00001 │
│ ^^^^ ^^^^^^^^^^ ^^^^^ │
│ BDK Device Counter=1 (first transaction) │
│ │
│ The KSN is sent WITH the encrypted data so the HSM knows │
│ which key to derive for decryption! │
│ │
└─────────────────────────────────────────────────────────────────────────┘
3. DUKPT Key Derivation Algorithm
Simplified DUKPT Derivation (the actual spec is in ANSI X9.24):
┌─────────────────────────────────────────────────────────────────────────┐
│ DUKPT DERIVATION STEPS │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ INITIAL KEY INJECTION (happens once at manufacturing): │
│ ═══════════════════════════════════════════════════════ │
│ │
│ 1. Extract device ID from KSN: FFFF 9876543210 00000 │
│ ^^^^^^^^^^ │
│ 2. Create registration value: 9876543210 || 0000000 (padded) │
│ │
│ 3. IPEK = 3DES_encrypt(registration, BDK) │
│ │
│ 4. Store IPEK in device's secure memory │
│ (BDK is NEVER stored in device!) │
│ │
│ ───────────────────────────────────────────────────────────────────── │
│ │
│ TRANSACTION KEY DERIVATION (happens per transaction): │
│ ═══════════════════════════════════════════════════════ │
│ │
│ Given: Counter = 0x00005 (binary: 0000 0000 0101) │
│ │
│ 1. Find set bits in counter: bits 0 and 2 │
│ │
│ 2. Start with current_key = IPEK │
│ │
│ 3. For each set bit (from left to right): │
│ - Create key_register = counter with only this bit set │
│ - current_key = derive_key(current_key, key_register) │
│ │
│ 4. Result: transaction_key for this counter │
│ │
│ derive_key(): │
│ - XOR key_register into current_key_right_half │
│ - Encrypt with current_key_left_half │
│ - Result is new key │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Why this design?:
- Forward secrecy: Compromised key doesn’t reveal past keys
- No reverse derivation: Can’t derive IPEK from transaction key
- Efficient: Only 21 derivations needed for 1M transactions
- Stateless HSM: HSM derives key on-demand from KSN + BDK
4. Hardware Security Modules (HSMs)
An HSM is a dedicated cryptographic processor with physical security protections.
HSM Properties:
- Tamper-resistant: Physical attacks destroy keys
- FIPS 140-2/3 certified: Validated security
- Key management: Keys generated, stored, and used inside HSM
- Audit logging: All operations logged
┌─────────────────────────────────────────────────────────────────────────┐
│ HSM TRUST BOUNDARY │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ HSM │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ SECURE ENCLAVE │ │ │
│ │ │ │ │ │
│ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │
│ │ │ │ BDK │ │ KEK │ │ Other │ │ │ │
│ │ │ │ Store │ │ Store │ │ Keys │ │ │ │
│ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │
│ │ │ │ │ │
│ │ │ ┌───────────────────────────────────────────────────┐ │ │ │
│ │ │ │ CRYPTO ENGINE │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ • 3DES / AES operations │ │ │ │
│ │ │ │ • DUKPT key derivation │ │ │ │
│ │ │ │ • PIN translation │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ Keys NEVER leave this boundary in plaintext │ │ │ │
│ │ │ └───────────────────────────────────────────────────┘ │ │ │
│ │ │ │ │ │
│ │ └──────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌──────────────────────────▼──────────────────────────────────┐ │ │
│ │ │ API LAYER │ │ │
│ │ │ │ │ │
│ │ │ decrypt_with_dukpt(encrypted_data, ksn) → plaintext │ │ │
│ │ │ encrypt_with_dukpt(plaintext, ksn) → encrypted_data │ │ │
│ │ │ translate_pin_block(encrypted_pin, ksn) → translated_pin │ │ │
│ │ │ │ │ │
│ │ │ Input: encrypted data goes IN │ │ │
│ │ │ Output: plaintext comes OUT (or processed result) │ │ │
│ │ │ Keys: NEVER come out │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
│ PHYSICAL SECURITY: │
│ • Tamper-evident seals │
│ • Intrusion detection (zeroizes keys if opened) │
│ • Environmental sensors (voltage, temperature) │
│ • FIPS 140-2 Level 3 or Level 4 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
5. Transaction Counter and Key Exhaustion
The Counter Problem:
- DUKPT counter is 21 bits → max 2,097,152 transactions
- After exhaustion, device must be re-keyed
Counter Management:
Counter: 0x00000 → First transaction
Counter: 0x00001 → Second transaction
Counter: 0x00002 → Third transaction
...
Counter: 0x1FFFFF → Last transaction (key exhausted!)
What happens at exhaustion:
- Device refuses to encrypt
- Alert sent to management system
- Device must be returned for re-keying (new IPEK injection)
Project Specification
What You’ll Build
A P2PE simulator with:
- Simulated Terminal: Encrypts “card swipes” using DUKPT
- Simulated HSM: Decrypts using BDK and KSN
- Trust Boundary Enforcement: Code that “cannot” access plaintext outside HSM
- Key Injection Ceremony: Initial device provisioning
- Transaction Flow Visualization: See encryption state at each step
Expected Output
# Initialize the system
$ ./p2pe init
[HSM] Generating Base Derivation Key...
[HSM] BDK created: FFFF (identifier)
[KEY CEREMONY] Starting key injection for terminal TRM001...
[KEY CEREMONY] Deriving IPEK from BDK + Device ID...
[TERMINAL TRM001] IPEK received and stored securely
[SYSTEM] P2PE system initialized
# Simulate a card swipe
$ ./p2pe swipe --terminal TRM001 --pan 4532015112830366
╔═══════════════════════════════════════════════════════════════════════╗
║ P2PE TRANSACTION FLOW ║
╠═══════════════════════════════════════════════════════════════════════╣
║ ║
║ STEP 1: CARD READ ║
║ ───────────────── ║
║ Terminal TRM001 reads: 4532015112830366 ║
║ Track 2 data captured ║
║ ║
║ STEP 2: KEY DERIVATION (inside terminal) ║
║ ───────────────────────────────────────── ║
║ Current counter: 0x00017 ║
║ KSN: FFFF9876543210000017 ║
║ Deriving transaction key from IPEK... ║
║ Transaction key: [PROTECTED - inside terminal secure memory] ║
║ ║
║ STEP 3: ENCRYPTION (inside terminal) ║
║ ──────────────────────────────────── ║
║ Plaintext: 4532015112830366 ║
║ Algorithm: 3DES-CBC ║
║ Output: A7F3B2C1D4E5F687H9I0J1K2L3M4N5O6 ║
║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ║
║ This is all the merchant system sees! ║
║ ║
║ STEP 4: TRANSMISSION ║
║ ─────────────────── ║
║ ┌─────────────────────────────────────────────────────────┐ ║
║ │ TRANSACTION REQUEST │ ║
║ │ KSN: FFFF9876543210000017 │ ║
║ │ Encrypted Data: A7F3B2C1D4E5F687H9I0J1K2L3M4N5O6 │ ║
║ │ Merchant: MERCH001 │ ║
║ │ Amount: $50.00 │ ║
║ └─────────────────────────────────────────────────────────┘ ║
║ ║
║ STEP 5: HSM PROCESSING ║
║ ─────────────────────── ║
║ [HSM] Received KSN: FFFF9876543210000017 ║
║ [HSM] Extracting BDK ID: FFFF ║
║ [HSM] Looking up BDK...found ║
║ [HSM] Deriving transaction key from BDK + KSN... ║
║ [HSM] Decrypting inside secure boundary... ║
║ [HSM] Plaintext recovered (inside HSM only): 4532015112830366 ║
║ [HSM] Forwarding to issuer for authorization... ║
║ ║
║ STEP 6: RESPONSE ║
║ ──────────────── ║
║ Authorization: APPROVED ║
║ Auth Code: 123456 ║
║ ║
║ Counter incremented: 0x00017 → 0x00018 ║
║ Remaining transactions: 2,097,128 ║
║ ║
╚═══════════════════════════════════════════════════════════════════════╝
# Verify merchant cannot decrypt
$ ./p2pe merchant-view --transaction 12345
╔═══════════════════════════════════════════════════════════════════════╗
║ MERCHANT SYSTEM VIEW ║
╠═══════════════════════════════════════════════════════════════════════╣
║ ║
║ Transaction ID: 12345 ║
║ Terminal: TRM001 ║
║ Timestamp: 2024-01-15 14:32:17 ║
║ ║
║ Card Data: A7F3B2C1D4E5F687H9I0J1K2L3M4N5O6 (encrypted) ║
║ KSN: FFFF9876543210000017 ║
║ ║
║ ⚠️ Decryption key: NOT AVAILABLE ║
║ ⚠️ Plaintext PAN: NOT AVAILABLE ║
║ ║
║ The merchant system has NO access to: ║
║ • The Base Derivation Key (BDK) ║
║ • The Initial PIN Encryption Key (IPEK) ║
║ • Any transaction key ║
║ ║
║ Even with full database access, the card number cannot be recovered! ║
║ ║
╚═══════════════════════════════════════════════════════════════════════╝
Project Structure
p2pe_simulator/
├── src/
│ ├── main.c # CLI entry point
│ ├── terminal/
│ │ ├── terminal.c # Terminal simulation
│ │ ├── terminal.h
│ │ ├── dukpt_encrypt.c # Encryption with DUKPT
│ │ └── secure_memory.c # Simulated secure memory
│ ├── hsm/
│ │ ├── hsm.c # HSM simulation
│ │ ├── hsm.h
│ │ ├── dukpt_decrypt.c # Decryption with DUKPT
│ │ ├── key_store.c # BDK storage
│ │ └── trust_boundary.c # Boundary enforcement
│ ├── key_management/
│ │ ├── key_ceremony.c # Key injection
│ │ ├── dukpt_derive.c # DUKPT algorithm
│ │ └── counter.c # Transaction counter
│ ├── transport/
│ │ ├── message.c # Transaction message format
│ │ └── protocol.c # Simulated network
│ └── crypto/
│ ├── des3.c # 3DES implementation
│ └── aes.c # AES for modern DUKPT
├── tests/
│ ├── test_dukpt.c
│ ├── test_terminal.c
│ ├── test_hsm.c
│ └── test_vectors/ # ANSI X9.24 test vectors
├── docs/
│ └── dukpt_explanation.md
├── Makefile
└── README.md
Core API Design
// terminal.h
typedef struct {
char terminal_id[16];
unsigned char ipek[16]; // Initial PIN Encryption Key
unsigned char ksn_base[10]; // KSN without counter
uint32_t counter; // Current transaction counter
} Terminal;
// Encrypt card data (happens inside terminal's secure boundary)
typedef struct {
unsigned char ciphertext[64];
size_t ciphertext_len;
unsigned char ksn[10]; // Full KSN with counter
} EncryptedTrack;
EncryptedTrack terminal_encrypt(Terminal* term, const char* track_data);
// hsm.h
typedef struct {
char hsm_id[16];
// BDK is stored inside and NEVER exposed
} HSM;
// Initialize HSM (loads BDK from secure storage)
HSM* hsm_init(const char* bdk_path);
// Decrypt (plaintext only exists inside this function)
// Returns authorization result, NOT the plaintext
typedef struct {
bool success;
char auth_code[8];
char error[64];
} AuthResult;
AuthResult hsm_process_transaction(HSM* hsm, const EncryptedTrack* track,
const char* merchant_id, int amount_cents);
// key_ceremony.h
// Generate IPEK for a new terminal (happens in secure facility)
bool key_ceremony_inject(HSM* hsm, Terminal* term, const char* device_id);
Solution Architecture
System Design
┌─────────────────────────────────────────────────────────────────────────┐
│ P2PE SIMULATOR ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ TERMINAL SIMULATOR │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ SECURE ELEMENT (simulated) │ │ │
│ │ │ │ │ │
│ │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │
│ │ │ │ IPEK │ │ Counter │ │ KSN Base │ │ │ │
│ │ │ │ (secret) │ │ Manager │ │ │ │ │ │
│ │ │ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │ │ │
│ │ │ │ │ │ │ │ │
│ │ │ └─────────────────┼─────────────────┘ │ │ │
│ │ │ │ │ │ │
│ │ │ ▼ │ │ │
│ │ │ ┌────────────────┐ │ │ │
│ │ │ │ DUKPT Engine │ │ │ │
│ │ │ │ (key derive + │ │ │ │
│ │ │ │ encrypt) │ │ │ │
│ │ │ └────────────────┘ │ │ │
│ │ │ │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ │ Encrypted output only │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ TERMINAL APPLICATION │ │ │
│ │ │ • Receives encrypted blob │ │ │
│ │ │ • Formats transaction message │ │ │
│ │ │ • NO access to plaintext or keys │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ {KSN, Encrypted Data, Amount} │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ MERCHANT SYSTEM (simulated) │ │
│ │ │ │
│ │ • Stores encrypted transaction │ │
│ │ • Forwards to processor │ │
│ │ • CANNOT decrypt (no keys) │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ {KSN, Encrypted Data, Amount} │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ HSM SIMULATOR │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ SECURE BOUNDARY │ │ │
│ │ │ │ │ │
│ │ │ ┌────────────┐ ┌────────────┐ │ │ │
│ │ │ │ BDK │ │ DUKPT │ │ │ │
│ │ │ │ Storage │───►│ Derive & │ │ │ │
│ │ │ │ │ │ Decrypt │ │ │ │
│ │ │ └────────────┘ └─────┬──────┘ │ │ │
│ │ │ │ │ │ │
│ │ │ ▼ │ │ │
│ │ │ ┌────────────────┐ │ │ │
│ │ │ │ PLAINTEXT │ ← Exists only here │ │ │
│ │ │ │ (in memory, │ │ │ │
│ │ │ │ for auth) │ │ │ │
│ │ │ └────────────────┘ │ │ │
│ │ │ │ │ │ │
│ │ │ ▼ │ │ │
│ │ │ ┌────────────────┐ │ │ │
│ │ │ │ Forward to │ │ │ │
│ │ │ │ Card Network │ │ │ │
│ │ │ └────────────────┘ │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ │ Auth result (no plaintext) │ │
│ │ ▼ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Trust Boundary Enforcement
How to simulate HSM trust boundary in software:
// trust_boundary.c
// All code that touches plaintext must be in this file
// This simulates "inside the HSM"
// Private - never expose
static unsigned char bdk[16];
// The ONLY function that can see plaintext
// Notice: it returns AuthResult, NOT the plaintext
static AuthResult process_inside_hsm(const EncryptedTrack* track) {
// 1. Derive key from KSN
unsigned char transaction_key[16];
dukpt_derive_key(bdk, track->ksn, transaction_key);
// 2. Decrypt
char plaintext[64];
des3_decrypt(track->ciphertext, transaction_key, plaintext);
// 3. Process (send to card network)
AuthResult result = send_to_issuer(plaintext);
// 4. CRITICAL: Zero plaintext before returning
explicit_bzero(plaintext, sizeof(plaintext));
explicit_bzero(transaction_key, sizeof(transaction_key));
// 5. Return only the auth result
return result;
}
// Public API - what outside code can call
AuthResult hsm_process_transaction(HSM* hsm, const EncryptedTrack* track,
const char* merchant_id, int amount_cents) {
// Validate inputs
// Log the request (without plaintext!)
// Call the internal function
return process_inside_hsm(track);
}
Implementation Guide
Phase 1: DUKPT Key Derivation
Goal: Implement the core DUKPT algorithm.
Start with test vectors from ANSI X9.24:
// Known test vectors
const char* BDK = "0123456789ABCDEFFEDCBA9876543210";
const char* KSN = "FFFF9876543210E00001";
// Expected IPEK: 6AC292FAA1315B4D858AB3A3D7D5933A
// Expected transaction key: depends on counter
Key derivation steps:
void dukpt_derive_ipek(const unsigned char* bdk,
const unsigned char* ksn,
unsigned char* ipek) {
// 1. Mask KSN: zero out counter bits
unsigned char masked_ksn[10];
memcpy(masked_ksn, ksn, 10);
masked_ksn[7] &= 0xE0; // Clear counter bits
masked_ksn[8] = 0x00;
masked_ksn[9] = 0x00;
// 2. Left half of IPEK
unsigned char data[8];
memcpy(data, masked_ksn, 8);
des3_encrypt(data, bdk, ipek); // Left 8 bytes
// 3. Right half of IPEK
unsigned char bdk_variant[16];
xor_with_constant(bdk, 0xC0C0C0C000000000C0C0C0C000000000, bdk_variant);
des3_encrypt(data, bdk_variant, ipek + 8); // Right 8 bytes
}
Phase 2: Terminal Simulation
Goal: Build terminal that encrypts card data.
Terminal secure memory simulation:
typedef struct {
unsigned char ipek[16];
uint32_t counter;
unsigned char ksn_base[10];
bool initialized;
} SecureMemory;
// Simulated secure memory - in real terminal this is in hardware
static SecureMemory secure_mem;
// Initialize with key ceremony
void terminal_init(const unsigned char* ipek, const unsigned char* ksn_base) {
memcpy(secure_mem.ipek, ipek, 16);
memcpy(secure_mem.ksn_base, ksn_base, 10);
secure_mem.counter = 0;
secure_mem.initialized = true;
}
Phase 3: HSM Simulation
Goal: Build HSM that decrypts but enforces trust boundary.
Key insight: The HSM function should NEVER return plaintext.
// BAD - exposes plaintext
char* hsm_decrypt(const EncryptedTrack* track) {
// This would violate the trust boundary!
return plaintext; // NEVER do this
}
// GOOD - processes but doesn't expose
AuthResult hsm_process(const EncryptedTrack* track) {
char plaintext[64];
decrypt_internally(track, plaintext);
// Do something with plaintext (auth check)
AuthResult result = authorize(plaintext);
// Zero before return
explicit_bzero(plaintext, 64);
return result; // Only return auth result
}
Phase 4: Key Injection Ceremony
Goal: Simulate initial device provisioning.
bool key_ceremony(HSM* hsm, Terminal* term, const char* device_id) {
// In reality, this happens in a secure room with split knowledge
// 1. Generate IPEK from BDK + device ID
unsigned char ipek[16];
unsigned char ksn_base[10];
// Build KSN base from BDK ID + Device ID
build_ksn_base(hsm->bdk_id, device_id, ksn_base);
// Derive IPEK (happens inside HSM)
hsm_derive_ipek(hsm, ksn_base, ipek);
// 2. "Inject" IPEK into terminal
// In reality: secure cable, no network, audited room
terminal_inject_ipek(term, ipek, ksn_base);
// 3. Zero the IPEK outside HSM
explicit_bzero(ipek, 16);
return true;
}
Phase 5: Transaction Flow
Goal: Full end-to-end transaction demonstration.
void demo_transaction(Terminal* term, HSM* hsm,
const char* pan, int amount_cents) {
printf("=== P2PE Transaction Demo ===\n");
// Step 1: Terminal encrypts
printf("[TERMINAL] Encrypting PAN: %s\n", pan);
EncryptedTrack encrypted = terminal_encrypt(term, pan);
printf("[TERMINAL] KSN: ");
print_hex(encrypted.ksn, 10);
printf("[TERMINAL] Ciphertext: ");
print_hex(encrypted.ciphertext, encrypted.ciphertext_len);
// Step 2: "Send" to processor (just pass the struct)
printf("[MERCHANT] Forwarding encrypted data (cannot decrypt!)\n");
// Step 3: HSM processes
printf("[HSM] Processing transaction...\n");
AuthResult result = hsm_process_transaction(hsm, &encrypted,
"MERCH001", amount_cents);
// Step 4: Show result
if (result.success) {
printf("[HSM] APPROVED - Auth code: %s\n", result.auth_code);
} else {
printf("[HSM] DECLINED - %s\n", result.error);
}
}
Testing Strategy
DUKPT Test Vectors
Use ANSI X9.24 test vectors to verify your implementation:
// test_dukpt.c
void test_ipek_derivation() {
// ANSI X9.24 test vector
unsigned char bdk[] = {0x01,0x23,0x45,0x67,0x89,0xAB,0xCD,0xEF,
0xFE,0xDC,0xBA,0x98,0x76,0x54,0x32,0x10};
unsigned char ksn[] = {0xFF,0xFF,0x98,0x76,0x54,0x32,0x10,0xE0,0x00,0x00};
unsigned char ipek[16];
dukpt_derive_ipek(bdk, ksn, ipek);
unsigned char expected[] = {0x6A,0xC2,0x92,0xFA,0xA1,0x31,0x5B,0x4D,
0x85,0x8A,0xB3,0xA3,0xD7,0xD5,0x93,0x3A};
assert(memcmp(ipek, expected, 16) == 0);
}
Trust Boundary Tests
// Verify plaintext never escapes HSM
void test_plaintext_not_exposed() {
// Set up terminal and HSM
// Process a transaction
// Check that no function returned plaintext
// Check that logs don't contain plaintext
}
Counter Management Tests
void test_counter_increments() {
Terminal term;
terminal_init(&term, test_ipek, test_ksn);
assert(term.counter == 0);
terminal_encrypt(&term, "4111111111111111");
assert(term.counter == 1);
}
void test_key_exhaustion() {
Terminal term;
terminal_init(&term, test_ipek, test_ksn);
term.counter = 0x1FFFFF; // Max counter
EncryptedTrack result = terminal_encrypt(&term, "4111111111111111");
assert(result.ciphertext_len == 0); // Should fail
}
Common Pitfalls & Debugging
Pitfall 1: Wrong Bit Ordering in KSN
Symptom: Derived keys don’t match test vectors.
Cause: DUKPT uses big-endian for KSN, your system might be little-endian.
Debug: Print each step and compare with manual calculation.
Pitfall 2: Counter Bit Extraction
Symptom: Keys derive incorrectly for counters > 1.
Cause: Misunderstanding which bits to use for each derivation step.
Fix: Counter bits are used right-to-left, each set bit triggers a derivation.
Pitfall 3: Exposing Plaintext
Symptom: Your “HSM” returns decrypted data.
Cause: Wrong API design—HSM should return auth result, not plaintext.
Fix: Refactor so all plaintext handling is inside one function.
Extensions & Challenges
Extension 1: AES-DUKPT
Implement the newer AES-based DUKPT (ANSI X9.24-3):
- 256-bit keys
- AES-128 block cipher
- Different derivation algorithm
Extension 2: PIN Blocks
Implement PIN encryption and translation:
- ISO Format 0/1/3 PIN blocks
- PIN translation between zones
Extension 3: EMV Cryptograms
Simulate EMV chip card cryptogram generation:
- Application Cryptogram (AC)
- Authorization Request Cryptogram (ARQC)
Extension 4: Multi-HSM
Implement HSM clustering:
- Key synchronization
- Failover handling
Interview Questions This Prepares You For
- “What is DUKPT and why is it used?”
- Derived Unique Key Per Transaction—enables unique encryption keys without distributing new keys
- “How does P2PE reduce PCI scope?”
- Merchant never has access to plaintext or keys, so their systems are out of scope
- “What’s an HSM and why is it necessary?”
- Hardware Security Module—provides physical security for keys, ensures keys never exist in regular memory
- “What happens when a DUKPT counter exhausts?”
- Device can no longer encrypt, must be re-keyed (new IPEK injection)
- “How do you derive a transaction key from IPEK?”
- Walk through the future key tree based on counter bits
Resources
Specifications
- ANSI X9.24-1: DUKPT (2017 version)
- ANSI X9.24-3: AES DUKPT
- PCI P2PE Standard
Books
| Topic | Book | Chapter |
|---|---|---|
| Key Derivation | Serious Cryptography (Aumasson) | Ch. 8: KDFs |
| HSM Concepts | Security in Computing (Pfleeger) | HSM section |
| Block Ciphers | Practical Cryptography (Ferguson) | Ch. 3-4 |
Articles
- “Understanding DUKPT” - Thales blog
- PCI P2PE Implementation Guide
Self-Assessment Checklist
- DUKPT IPEK derivation matches test vectors
- Transaction key derivation works for all counter values
- Terminal encrypts without exposing keys
- HSM decrypts without exposing plaintext
- Counter management prevents key reuse
- Key exhaustion is handled gracefully
- Can explain the security model to others
- Understand why merchant can’t decrypt
What’s Next?
You now understand card-present security. For e-commerce (card-not-present), move to Project 4: 3D Secure Authentication Flow to learn how online transactions add cardholder authentication.