How to Debug APDU Responses — SW1 SW2 Status Word Reference
Every APDU command returns a 2-byte status word (SW1 SW2). 90 00 means success. Everything else is an error or warning. This guide is a ISO 7816-4 status word reference organized by SW1 class, with practical debugging strategies for real-world card interactions: T=0 GET RESPONSE flow, T=1 chaining, security state, and file system errors.
1. SW1 SW2 Encoding
| SW1 Range | Category | Meaning |
|---|---|---|
| 0x90 | Normal | Success; SW2 varies (0x00 = complete, non-zero = proprietary info) |
| 0x61 | Normal | Data available via GET RESPONSE; SW2 = byte count |
| 0x62 | Warning | State of non-volatile memory unchanged (card accepted but with caveat) |
| 0x63 | Warning | State of non-volatile memory changed (card accepted but with caveat) |
| 0x64 | Execution Error | State of non-volatile memory unchanged (card rejected) |
| 0x65 | Execution Error | State of non-volatile memory changed (card rejected) |
| 0x66 | Security Error | Security-related issue |
| 0x67-0x6B | Checking Error | Wrong Lc/Le/P1/P2, or command not supported |
| 0x6C | Checking Error | Wrong Le; SW2 = correct Le value |
| 0x6D-0x6F | Checking Error | Instruction not supported / class not supported / no precise diagnosis |
2. Most Common Status Words — Quick Reference
| SW1 SW2 | Mnemonic | Meaning | What to Do |
|---|---|---|---|
| 90 00 | SUCCESS | Command executed successfully | Done |
| 61 XX | DATA_AVAILABLE | XX bytes ready for GET RESPONSE (T=0) | Send 00 C0 00 00 XX |
| 62 83 | CHV_BLOCKED | PIN blocked (counter at zero) | Use PUK or unblock command |
| 63 CX | CHV_WARNING | PIN verify failed; X = remaining attempts | Retry with correct PIN; card locks after 0 |
| 67 00 | WRONG_LENGTH | Wrong Lc (command data length) | Check Lc against file/command spec |
| 69 82 | SECURITY_NOT_SATISFIED | PIN not verified / access condition not met | Verify PIN/authenticate first |
| 69 84 | DATA_INVALIDATED | Data or key referenced is invalid | Check key reference / data object exists |
| 69 85 | CONDITIONS_NOT_SATISFIED | Pre-conditions not met | Select correct DF / authenticate |
| 6A 80 | WRONG_DATA | Incorrect data in command data field | Verify data format/syntax |
| 6A 82 | FILE_NOT_FOUND | File/application not found | Check FID/AID, select parent DF first |
| 6A 86 | WRONG_P1P2 | Incorrect P1 or P2 | Verify P1P2 against command spec |
| 6A 88 | REF_DATA_NOT_FOUND | Referenced data (key/PIN) not found | Check key/PIN reference number |
| 6B 00 | WRONG_LE | Wrong Le (expected length) | Verify requested length ≤ file size |
| 6C XX | WRONG_LE_CORRECT | Correct Le is XX | Retry command with Le = XX |
| 6D 00 | INS_NOT_SUPPORTED | Instruction code not supported | Check card spec for supported INS values |
| 6E 00 | CLA_NOT_SUPPORTED | Class byte not supported | Check card spec for supported CLA values |
| 6F 00 | NO_PRECISE_DIAGNOSIS | Generic error, no further info | Retry with different parameters; check card logs |
3. T=0 GET RESPONSE Flow
T=0 protocol is the #1 source of confusion. When the card wants to return data but the command didn't specify Le, or data exceeds the buffer:
# STEP 1: Send command WITHOUT Le >> 00 A4 04 00 07 A0 00 00 00 03 10 10 # SELECT AID, no Le << 61 1A # "I have 0x1A (26) bytes — ask for them with GET RESPONSE" # STEP 2: Send GET RESPONSE with correct Le >> 00 C0 00 00 1A # GET RESPONSE, Le = 0x1A << 6F 15 84 07 ... 90 00 # FCI template data, followed by 90 00
A robust function should handle 61XX and 6CXX automatically:
def send_apdu_robust(connection, apdu):
"""Send APDU, auto-handle T=0 GET RESPONSE and wrong Le."""
resp, sw1, sw2 = connection.transmit(apdu)
while True:
if sw1 == 0x61:
# Data available: send GET RESPONSE
get_resp = [0x00, 0xC0, 0x00, 0x00, sw2]
more, sw1, sw2 = connection.transmit(get_resp)
resp += more # Append response data
elif sw1 == 0x6C:
# Wrong Le: retry with correct Le from SW2
apdu[-1] = sw2 # Update Le byte
resp, sw1, sw2 = connection.transmit(apdu)
else:
break
return resp, sw1, sw2
4. Security State Debugging
Many errors come from incomplete security state. The card maintains a security status (PIN verified, external auth done, secure channel open) that resets on DF change or card reset:
# Common security flow: # 1. SELECT application (MF or AID) # 2. VERIFY PIN (if needed) → card now has "PIN verified" state # 3. EXTERNAL AUTHENTICATE (if GP secure channel) → "auth" state # 4. Now commands that require security state (6xxx errors) will work # If you get 6982 (SECURITY_NOT_SATISFIED): # → You forgot to verify PIN or authenticate first # If you get 6985 (CONDITIONS_NOT_SATISFIED): # → Wrong DF selected, or security state was reset by DF selection
5. Debugging Workflow
# 1. Start with a known-good SELECT command: SELECT_MF = [0x00, 0xA4, 0x00, 0x00, 0x02, 0x3F, 0x00] # Expected: 90 00 (JTAPI/Gemalto cards) or 61 XX + GET RESPONSE # 2. If 6D 00 → card doesn't support that INS # If 6E 00 → card doesn't support that CLA (try 0x80 for GP cards) # If 6A 82 → file/AID doesn't exist on this card # 3. After SELECT, test with a simple READ BINARY: READ_10 = [0x00, 0xB0, 0x00, 0x00, 0x0A] # If 69 82 → need PIN/first; if 6A 82 → no EF selected
6982 or 6A82 and get human-readable explanation, common causes, and suggested fixes.Related Tools
APDU Response Debugger — Decode SW1 SW2 instantly | APDU Command Builder — Build correct commands | APDU Quick Reference — All ISO 7816-4 INS codes | ISO 7816 Protocol Reference