JavaCard Applet Development for Enterprise Identity
Smart-card programmes hit a wall the moment the off-the-shelf applets don’t do quite what the enterprise needs. JavaCard is the answer. This is what shipping an enterprise applet actually involves — from AID design to personalisation hooks to the GlobalPlatform plumbing.
If you are at the point of asking "can we add this custom behaviour to our identity card", you are at the point of considering a JavaCard applet. This post is for the people who have shipped a Java microservice in the past and are about to discover that JavaCard is a different language with the same name. It will save you a few weeks.
What JavaCard actually is
JavaCard is a stripped-down Java for smart-card chips. The runtime is a few kilobytes; there is no garbage collector (mostly); the standard library is tiny and crypto-focused; primitive types are limited (typically byte, short, sometimes int); object allocation in the steady state is forbidden.
What you get in exchange:
- A hardware-isolated execution environment running on an EAL5+ secure element.
- Standardised APDU communication via the JavaCard framework.
- A standardised lifecycle: install, personalise, lock, terminate.
- The GlobalPlatform card-management protocol for installing and updating applets in the field.
- Hardware cryptographic primitives exposed via
javacard.security— AES, ECC, RSA, hashing — running on the chip’s crypto coprocessor, not in interpreted bytecode.
Think of it as embedded development with a Java surface and very strict memory rules.
When you actually need an applet
Most enterprise-identity programmes can be built with off-the-shelf applets: FIDO2, PIV, OpenPGP, NDEF, plus a closed-loop or transit applet. You write a custom applet when:
- You need a combination of behaviours that no standard applet offers (e.g. FIDO2 + a proprietary access-control protocol on the same card).
- You have regulatory or scheme-specific requirements the off-the-shelf applets don’t meet.
- You need to integrate with a legacy reader protocol that’s never been standardised.
- You want card-side policy — counters, velocity limits, geofence-aware permission gates — that lives on the card rather than the backend.
If none of these apply, save yourself the effort and use a standard applet.
AID design
Every applet is selected by its Application Identifier (AID), a 5-16 byte identifier in the SELECT APDU. The AID structure matters more than people initially think:
- The first 5 bytes are the RID (Registered application provider Identifier). If you want to be globally unambiguous, register your RID with the ISO body. Many enterprises use a private RID and accept that it’s administratively, not formally, unique.
- The remaining 0-11 bytes are the PIX (Proprietary application Identifier eXtension). Use this to encode your applet version, target use, or instance identifier.
A few rules in practice:
- Encode the version in the AID. When you ship v2 of your applet, the v1 AID and the v2 AID should be different. This lets you co-resident both on transition cards.
- Don’t collide with well-known AIDs. The FIDO2 AID, the PIV AID, EMV AIDs — all reserved. Use a different RID.
- Keep the AID short. Some readers truncate at 16 bytes regardless of spec.
Applet lifecycle
JavaCard applet lifecycle is rigid and well-defined:
- Load. The CAP file (compiled applet) is loaded onto the card via GlobalPlatform’s
LOADcommand. The applet now exists as code on the card. - Install. An instance of the applet is created.
install()runs on the card. Persistent storage is allocated. The applet is registered against its AID. - Selectable. The applet can be selected by AID and respond to APDUs.
- Personalise. The applet is given its per-card data — keys, certificates, user identifiers. This typically happens at the personalisation line, under SCP02 / SCP03 secure messaging.
- Locked / Terminated. Depending on use case, the applet may be locked (no further personalisation) or terminated (no further use, ever).
The applet author writes code for install(), select(), deselect(), and process() (the APDU dispatcher). The framework calls these at the right state transitions.
Memory rules
The single biggest culture shock for developers coming from server-side Java. JavaCard has three memory regions:
- Persistent (EEPROM / Flash). Survives power loss. Slow to write. Limited write-cycle endurance (typically 100k-500k cycles). Holds everything that has to live across power events — keys, certificates, counters.
- Transient (RAM). Lost on power loss or applet deselect. Fast. Used for session state, scratch buffers, derived session keys.
- Code. The CAP file itself, immutable after load.
Rules to live by:
- Allocate everything in
install(). Onceinstall()returns, the steady-state applet should not allocate. Newnewinprocess()is the canonical mistake. - Use
JCSystem.makeTransientByteArray()for scratch buffers. These live in RAM, are fast, and can be reused. - Treat persistent writes as expensive. Batch writes into transactions. Use
JCSystem.beginTransaction()/commitTransaction()to make multi-field updates atomic. - Watch your counter granularity. A counter that increments on every operation eats your EEPROM endurance. Cache in RAM and flush on deselect, or only persist every N operations.
APDU dispatch
Inside process(APDU apdu) the applet inspects the CLA / INS bytes and dispatches:
public void process(APDU apdu) {
if (selectingApplet()) return; // standard handshake
byte[] buf = apdu.getBuffer();
if (buf[ISO7816.OFFSET_CLA] != PROPRIETARY_CLA) {
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
}
switch (buf[ISO7816.OFFSET_INS]) {
case INS_GET_DATA: doGetData(apdu); break;
case INS_PUT_DATA: requireSecureMessaging(apdu); doPutData(apdu); break;
case INS_VERIFY_PIN: doVerifyPin(apdu); break;
case INS_SIGN: requireUserAuthed(); doSign(apdu); break;
default: ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
}
Notice the explicit checks. Every privileged operation has a precondition (secure messaging, user authentication, an unlocked state). The dispatcher enforces them before the handler runs. This is the foundation of secure applet design.
Secure messaging (SCP02 / SCP03)
An applet personalised at the line and then handed to a user has to know that any further administrative APDU comes from an authorised issuer. GlobalPlatform’s SCP02 and SCP03 are the standardised secure-messaging protocols for this.
SCP03 in particular is the modern default: AES-128 / AES-256 session keys derived from issuer keys, MAC’d and optionally encrypted APDUs. The applet decrypts and verifies before acting.
What you have to get right:
- Refuse plain-text administrative APDUs after personalisation. If the personalisation step set a "personalised" flag, any subsequent
PUT DATAoutside SCP03 must be rejected. - Distinguish user-mode APDUs from issuer-mode APDUs. User-mode (sign, verify PIN) is fine in plain text. Issuer-mode (update key, add credential) requires SCP03.
- Handle session-key derivation correctly. The session key has to be derived per session from the static issuer key. Caching it across sessions is a vulnerability.
Personalisation
Personalisation is the bridge between the applet on the card and the rest of the world.
For an enterprise identity applet, personalisation typically writes:
- The user’s identifier (employee ID, account UUID).
- The user’s public certificate (or trust anchor, if the cert is generated on-card).
- Initial PIN policy (length, retry counter).
- Per-card derived keys (or seeds the applet uses to generate them).
Personalisation runs over SCP03 secure messaging, against the issuer key set installed at GP load time. The personalisation script is owned by the issuer; it never leaves the personalisation line in cleartext.
Testing
JavaCard applets are tested in three places, in order of fidelity:
- jCardSim or similar emulator. A pure-Java simulation of the JavaCard runtime. Fast iteration; misses chip-specific behaviour. Use for unit tests of business logic.
- Real card + APDU tooling.
gpshell,pcsc-tools, custom scripts. Slower, but catches issues the emulator hides (transient memory exhaustion, real crypto coprocessor behaviour, GP keyset issues). - Personalisation rig + full integration. The end-to-end flow your production line will run. This is where the long-tail bugs surface.
The mistake to avoid: assuming the emulator is enough. It catches 80% of logic bugs and 0% of real-card bugs.
Security-critical patterns
Constant-time comparison
Comparing a user-supplied PIN to a stored PIN with Util.arrayCompare() short-circuits on the first mismatch and leaks timing. Use the framework’s OwnerPIN class, which is designed to be constant-time, or implement your own with explicit XOR loops.
Retry counters
Always decrement the retry counter before attempting the comparison. If power is yanked mid-operation, the counter has to have already decremented or the attacker can run indefinitely.
Atomic state changes
Multi-field state transitions (e.g. "set key" + "set certificate" + "set PIN") have to be in a transaction. Power-loss between two of them must leave the applet in either the old state or the new state, never an in-between.
Side-channel resistance
The crypto primitives provided by the framework are implemented on the chip’s coprocessor with side-channel resistance baked in. Implementing your own crypto in JavaCard bytecode is almost always a mistake. Use the framework.
GlobalPlatform basics
GlobalPlatform is the card-management protocol you use to install your applet on the chip in production. Three concepts you have to internalise:
- Issuer Security Domain (ISD). The privileged applet that owns the card. Holds the master GP keys. Installs and removes applets.
- Supplementary Security Domain (SSD). A delegated domain installed under the ISD. Useful when multiple parties install applets on the same card; each gets its own SSD.
- Keysets. Per-domain key sets (KENC, KMAC, KDEK in SCP02 / SCP03). The personalisation line uses these to authenticate to the card before installing.
An enterprise issuance line typically holds its own SSD keyset, separate from the chip vendor’s ISD. This means the chip vendor can’t install applets without your involvement, and you can’t install applets at the ISD level without the chip vendor’s.
Related reading
- APDU From First Principles
- Designing Secure Credential Lifecycle Management
- Secure Element vs TPM vs HSM
- Technology: JavaCard
- Product: AmbiSecure JavaCard applets
- Service: JavaCard development
- Solution: JavaCard deployment
- Case study: Passwordless workforce