Ambimat GroupAmbimatAmbiSecureeSIM InitiativeEngineering BlogAhmedabad · India · Est. 1981

APDU from First Principles: CLA, INS, P1/P2, Le, Lc, and SW1/SW2

If you have ever poked at a smart card with PC/SC, gpshell, or OpenSC, you have seen a stream of hex bytes labelled APDUs. This is what those bytes actually mean, when each form is used, and which mistakes keep biting people in production.

This post is the cornerstone of our APDU cluster. The companion utility tools are linked throughout: paste your hex into the APDU parser as you read.

What an APDU actually is

APDU stands for Application Protocol Data Unit. It is the wire format for messages between a smart-card terminal (the reader) and the application running on the chip (the card). It is defined in ISO/IEC 7816-4, and used by every contact and contactless smart-card application in production: EMV banking cards, FIDO authenticators, enano-card applets, transit cards, SIMs, government eIDs.

Two kinds of APDU exist:

  • Command APDU — what the reader sends to the card.
  • Response APDU — what the card sends back, ending always with two status bytes (SW1 and SW2).

Command APDU shape

A command APDU is at minimum a 4-byte header:

// Header: 4 bytes
CLA  INS  P1  P2

Followed by zero, one, or both of:

  • Lc + Data — outbound data the reader is sending to the card. Lc is the length.
  • Le — the maximum length of response data the reader expects.

Combinations give the four cases:

CaseLayoutWhat it means
Case 1CLA INS P1 P2No data going either direction. Almost a "ping".
Case 2CLA INS P1 P2 LeNo data sent. Reader is asking for up to Le bytes back.
Case 3CLA INS P1 P2 Lc DataReader sends Lc bytes; expects only SW1/SW2 back.
Case 4CLA INS P1 P2 Lc Data LeBoth directions.

CLA — the class byte

CLA tells the card how to interpret the command and what context it is in (channel, secure messaging, proprietary or inter-industry).

// CLA bit layout for inter-industry commands (b8..b1)
b8 b7 b6 b5  command class
            0000 = inter-industry, ISO/IEC 7816-4
            10xx = proprietary (GP often uses 0x80)
b4 b3        secure messaging
            00 = none
            10 = header authenticated
            11 = full secure messaging (cmd + body)
b2 b1        logical channel (0..3)

So 0x00 = inter-industry, no secure messaging, channel 0. 0x80 = proprietary (GlobalPlatform style), channel 0. 0x84 = proprietary, header-authenticated SM, channel 0. 0x0C = inter-industry with full SM, channel 0.

INS — the instruction byte

INS is the actual operation code. ISO 7816-4 defines a "global" set; individual specs (PIV, OpenPGP, EMV, FIDO/CTAP1) define their own. Common ones:

INSOperation
0x20VERIFY (PIN / CHV)
0x22MANAGE SECURITY ENVIRONMENT
0x84GET CHALLENGE
0x88INTERNAL AUTHENTICATE
0xA4SELECT (file / AID)
0xB0READ BINARY
0xB2READ RECORD
0xC0GET RESPONSE
0xCAGET DATA
0xD6UPDATE BINARY

Convention: even INS values are most common; odd INS often indicates a variant of the same operation (extended addressing, alternative encoding).

P1 and P2 — instruction parameters

P1 and P2 are two opaque bytes whose meaning is defined per-INS. For SELECT (INS=0xA4), P1 selects the addressing mode and P2 controls the response format:

// SELECT P1 = 04 (Select by DF name / AID)
// SELECT P2 = 00 (return FCI; first or only occurrence)
00 A4 04 00 07 A0 00 00 00 03 10 10 00
^^ ^^ ^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^ ^^
CLA INS P1 P2 Lc          AID         Le

This is a real EMV Visa "select PSE" command. Lc=7 because the AID is 7 bytes; Le=00 means "give me up to 256 bytes back". The card will respond with an FCI template (BER-TLV with tag 6F) — paste a real one into our TLV parser if you want to see it decoded.

Lengths: Lc, Le, and the difference between 0 and 256

Three things to internalise about lengths:

1. Lc is mandatory iff data is present

If you are sending data, you need exactly one Lc byte (short form) or three Lc bytes (extended form, see below). If not, you don’t.

2. Le has a special "all of it" encoding

In short form, Le = 0x00 does not mean "zero bytes". It means "as many as the card will return, up to 256". This trips people up. Le = 0x01 means exactly one byte. There is no way to ask for "exactly zero" with Le present — drop Le entirely (Case 1 or Case 3) for that.

// Le = 0x00 -> ask for up to 256 bytes.
00 A4 04 00 07 A0 00 00 00 03 10 10 00

3. 61XX means "I have data for you, come get it"

If the card has more data than fits, it returns 61XX where XX is the number of bytes still available. The reader then issues GET RESPONSE (00 C0 00 00 XX) to retrieve them. Many older drivers wrap this transparently; many newer ones do not. If you see 6100 ignored in your logs, that is a bug.

Extended-length APDUs: when 256 bytes is not enough

Short-form Lc and Le max out at 255 and 256 respectively. Extended-length APDUs prefix Lc and Le with a 0x00 byte and use 2-byte big-endian values.

FormLc encodingLe encodingMax data per direction
Short1 byte (1..255)1 byte (1..256, 0=256)255 / 256
Extended Case 200 LeHi LeLo (1..65536)65536
Extended Case 300 LcHi LcLo (1..65535)65535
Extended Case 400 LcHi LcLoLeHi LeLo65535 / 65536

Extended-length is negotiated per-application. Many cards advertise support in their ATR historical bytes (the SELECT response FCI sometimes includes extended-length info too). Send extended-length to a card that only supports short-form and you will get back 6700 ("wrong length") at best, or a confused timeout at worst.

Response APDU shape

A response APDU is at minimum two bytes — SW1 SW2. Optional response data precedes them.

// Successful response with data:
[Data ... ] SW1 SW2

// Just status:
SW1 SW2

SW1 SW2 — status words

The two status bytes tell you what happened. The full table is in our APDU Status Dictionary. The 12 you must memorise:

SW1 SW2Meaning
9000OK. Universal success.
61XXOK; XX bytes are still available — issue GET RESPONSE.
6CXXWrong Le. The card tells you the right length is XX. Re-send with corrected Le.
6700Wrong length. (Generic; not as helpful as 6CXX.)
63CXVerify failed; X retries remaining (PIN).
6982Security status not satisfied. (Translation: log in first.)
6985Conditions of use not satisfied. (FIDO often: user-presence absent.)
6A82File not found. (Wrong AID, or app uninstalled.)
6A86Wrong P1/P2.
6A87Lc inconsistent with P1/P2.
6D00INS not supported. (Wrong applet for this command.)
6E00CLA not supported. (Wrong applet, or wrong channel mode.)

A real APDU flow

Here is the start of a Visa cardholder verification flow. Every byte matters.

// 1. Select the Payment System Environment (PSE):
>> 00 A4 04 00 0E 32 50 41 59 2E 53 59 53 2E 44 44 46 30 31 00
<< 6F 1E 84 0E 32 50 41 59 2E 53 59 53 2E 44 44 46 30 31 A5 0C 88 01 01 5F 2D 02 65 6E 90 00

// FCI in the response (tag 6F) lists EMV applications.
// Decode that with the TLV parser.

// 2. Select the AID we found inside the FCI:
>> 00 A4 04 00 07 A0 00 00 00 03 10 10 00
<< 6F 1E 84 07 A0 00 00 00 03 10 10 A5 13 50 0A 56 49 53 41 20 44 45 42 49 54 87 01 02 9F 38 03 9F 1A 02 90 00

// 3. GET PROCESSING OPTIONS — tell the card the terminal capabilities:
>> 80 A8 00 00 02 83 00 00
<< ...

// 4. READ RECORD ...

A real EMV transaction is hundreds of APDUs. The pattern is the same: SELECT to land on the right applet, then a sequence of operation-specific commands gated by status words.

Six pitfalls we have walked into

  1. Mixing CLA conventions. A GlobalPlatform tool sends 0x80 CLA; an EMV terminal sends 0x00. Send 0x80 to an inter-industry applet and you get 6E00.
  2. Forgetting 61XX chaining. Modern PC/SC stacks usually handle this. Embedded Linux drivers and Android NFC stacks often do not. If you have a 600-byte response and you only see 256 bytes, look for the missing GET RESPONSE.
  3. Treating Le=0x00 as zero. It means "give me up to 256 bytes". Many libraries get this wrong; the more careful ones expose a sentinel.
  4. Mixing short and extended length. Within a single APDU it is one or the other. Across APDUs in the same session you can mix only if the card declared support for both.
  5. Sending extended-length to a card that doesn’t support it. You don’t get a clean reject — sometimes you get 6700, sometimes a timeout, sometimes a confused state. Always check the ATR / SELECT FCI for extended-length capability.
  6. Ignoring secure messaging on a personalised card. Many issuer-personalised cards reject plain APDUs after personalisation. The applet is fine; you forgot to wrap the APDU in SCP02/SCP03 secure messaging.

Companion tools

What this means for your deployment

If you are integrating with a smart card or a JavaCard applet, treat APDUs as the truth and your higher-level library wrappers as conveniences. When something fails, the diagnostic is always: capture the wire APDU, decode it, decode the response, and look up the status word. The wrapper’s exception class is downstream of all of that.

The good news: APDU is a 30-year-old format, the spec is short, and the rules are stable. Once you have internalised CLA / INS / P1 / P2 / Lc / Le / SW1 / SW2 you can read any smart-card protocol on the wire — EMV, FIDO over CTAP1, PIV, OpenPGP, GP, eSIM, transit. They all use the same envelope.

Working on a smart-card or applet integration?

Talk to engineers, not BDRs. We have shipped FIDO, PIV, payment, and custom applets and personalisation tooling for a decade.

Start a conversation