How to Compute EMV Cryptograms — ARQC, ARPC, AAC, TC Step-by-Step
EMV transaction security hinges on the Application Cryptogram. The card generates an ARQC (Authorization Request Cryptogram) using a session key derived from the card master key + ATC. The issuer validates it and returns an ARPC. This guide walks through the full flow: CDOL1 data assembly, session key derivation, 3DES MAC computation, and CID/CVR decoding.
1. EMV Cryptogram Types
| Cryptogram | CID Value | Meaning |
|---|---|---|
| AAC | 0x00 | Application Authentication Cryptogram — transaction declined by card |
| TC | 0x40 | Transaction Certificate — transaction approved offline by card |
| ARQC | 0x80 | Authorization Request Cryptogram — card requests online authorization |
| ARPC | N/A | Authorization Response Cryptogram — issuer's response (ARQC validation proof) |
2. CDOL1 — Card Data for ARQC
The CDOL1 (Card Data Object List 1) tells the terminal which data elements to include in the GENERATE AC command. Typical CDOL1 from EMV tag 0x8C:
# CDOL1 example (from Visa card): # 9F02 06 → Amount, Authorized (6 bytes, numeric) # 9F03 06 → Amount, Other (6 bytes) # 9F1A 02 → Terminal Country Code (2 bytes) # 95 05 → Terminal Verification Results (5 bytes) # 5F2A 02 → Transaction Currency Code (2 bytes) # 9A 03 → Transaction Date (3 bytes) # 9C 01 → Transaction Type (1 byte) # 9F37 04 → Unpredictable Number (4 bytes) # CDOL1 data (29 bytes assembled for GENERATE AC): # 000000001000 000000000000 0840 0000000000 0840 250701 00 A1B2C3D4 # └─Amount────┘ └─Other Amt┘ └TCC┘ └─TVR─────┘ └TCC┘ └Date┘ └TT┘ └─UN────┘
3. GENERATE AC Command — Requesting the ARQC
# GENERATE AC command (EMV INS = 0xAE):
# CLA=0x80, INS=0xAE, P1=0x80 (ARQC), P2=0x00
# Data = CDOL1 data + optional Le
cdol1_data = bytes.fromhex(
"000000001000" # Amount Authorized: 100.00
"000000000000" # Amount Other: 0
"0840" # Terminal Country Code: USA (840)
"0000000000" # TVR: all zeros
"0840" # Currency Code: USD (840)
"250701" # Date: July 1, 2025
"00" # Transaction Type: Goods & Services
"A1B2C3D4" # Unpredictable Number
)
GENERATE_AC_ARQC = [0x80, 0xAE, 0x80, 0x00, len(cdol1_data)] + list(cdol1_data) + [0x00]
# → 80 AE 80 00 1D [29 bytes CDOL1 data] 00
4. The ARQC Response — Parsing
# Response to GENERATE AC:
# [Cryptogram Information Data (1 byte)] [ATC (2 bytes)] [ARQC (8 bytes)] [IAD] [SW1 SW2]
# Example: 80 00 01 C1 A2 B3 C4 D5 E6 F7 88 ...
# │ └ATC┘ └────── ARQC (8 bytes) ──────┘
# CID=0x80 (ARQC)
def parse_generate_ac_response(response):
cid = response[0]
atc = (response[1] << 8) | response[2]
arqc = response[3:11]
iad = response[11:] # Issuer Application Data (variable length)
return {
'cryptogram_type': 'ARQC' if cid == 0x80 else 'TC' if cid == 0x40 else 'AAC',
'atc': atc,
'arqc': arqc.hex().upper(),
'iad': iad.hex().upper() if iad else None
}
5. Computing the ARQC (Issuer-Side Verification)
The issuer verifies the ARQC by computing the same cryptogram from the session key and CDOL1+CDOL2 data:
from Crypto.Cipher import DES3
def compute_arqc(session_key, cdol1_data, cdol2_data, atc):
"""Compute ARQC using 3DES Retail MAC."""
# Step 1: Derive session key from MK + ATC
# SK = 3DES_MK( ATC || 0xF8 0x00 0x00 0x00 0x00 0x00 )
# Step 2: Build input data
# input = CDOL1_data || CDOL2_data || (CID || ATC || ...)
input_data = cdol1_data + cdol2_data
# Step 3: 3DES CBC-MAC with initial chaining value = 0x00...00
# Pad input to 8-byte boundary (EMV padding: 0x80 + 0x00s)
pad_len = 8 - (len(input_data) % 8)
input_data += b'\x80' + b'\x00' * (pad_len - 1)
# CBC encrypt with session key, IV=0
cipher = DES3.new(session_key, DES3.MODE_CBC, iv=b'\x00'*8)
encrypted = cipher.encrypt(input_data)
# ARQC = last 8 bytes of encrypted output
arqc = encrypted[-8:]
return arqc
6. ARPC — Issuer Response Cryptogram
The issuer validates the ARQC and returns an ARPC (Authorization Response Cryptogram):
# ARPC generation uses the same session key:
# ARPC_Method_1: MAC over (ARC || ARQC || ...)
# ARPC_Method_2: proprietary to payment scheme
def compute_arpc_method1(session_key, arqc, arc):
"""Compute ARPC Method 1."""
# ARC (Authorization Response Code): 2 bytes (e.g., "00" = approved)
input_data = arc.encode() + arqc
# Pad to 8-byte boundary with 0x80 0x00...
pad_len = 8 - (len(input_data) % 8)
input_data += b'\x80' + b'\x00' * (pad_len - 1)
cipher = DES3.new(session_key, DES3.MODE_CBC, iv=b'\x00'*8)
encrypted = cipher.encrypt(input_data)
return encrypted[-8:] # ARPC = last 8 bytes
7. Cryptogram Information Data (CID) and CVR
CID byte: [00] [CVR bit] [RFU] [...] [Cryptogram Type] Bit 7-6: Cryptogram Type (00=AAC, 01=TC, 10=ARQC) Bit 5-0: Application-specific CVR (Card Verification Results): 4 bytes in IAD # CVR Byte 1: # Bit 8: Offline data auth performed # Bit 7: SDA failed # Bit 6: DDA failed # Bit 5: Card appears on terminal exception list # Bit 4: CDA failed
Related Tools
EMV Cryptogram Visualizer — Step-by-step ARQC generation | Session Key Visualizer — 3DES/AES key derivation | EMV TLV Parser — Decode EMV card responses | EMV Tag Reference — Full data object list