Secure Element Programming: Applet Development for Java Card

Java Card is a technology that allows Java-based applications (applets) to run on smart cards, SIM cards, secure elements, and other highly constrained devices with limited memory and processing power. These applets handle security-critical tasks like payment authentication, identity verification, and access control. Developing for Java Card is fundamentally different from standard Java programming due to the extreme resource constraints and security focus.

This article provides a deep dive into the concepts, tools, and processes for developing applets for the Java Card platform.


1. Understanding the Java Card Environment

Before writing code, it's crucial to understand the target environment's constraints and architecture.

Key Characteristics:

  • Extreme Resource Constraints: Typically 16-512 KB of ROM, 1-8 KB of RAM, and 16-128 KB of EEPROM.
  • No Garbage Collection: Dynamic memory allocation is forbidden. All objects must be allocated once during the applet's installation.
  • Limited Language Features: No java.lang.String, no floating-point numbers, no threads, no reflection, and no large parts of the standard Java class library.
  • Single Instance: Only one instance of an applet exists on the card, and it must handle all commands sequentially.
  • Security-First Design: The entire platform is designed around secure execution and data isolation.

Java Card vs. Standard Java:

FeatureStandard JavaJava Card
Memory ManagementGarbage CollectedNo GC, static allocation only
Stringsjava.lang.Stringbyte[] or char[] (ISO 7816 format)
Floating PointSupportedNot supported
ThreadsSupportedNot supported
I/ORich APIs (Files, Network)APDU-based communication only

2. Core Java Card Applet Architecture

A Java Card applet follows a specific lifecycle and communication pattern based on the APDU (Application Protocol Data Unit) protocol.

The Applet Skeleton

Every Java Card applet must extend javacard.framework.Applet and implement key methods.

import javacard.framework.*;
import javacard.security.*;
import javacardx.crypto.*;
public class MyFirstApplet extends Applet {
// Constants for INS byte (instruction) in APDU
private static final byte INS_VERIFY_PIN = (byte) 0x20;
private static final byte INS_GET_BALANCE = (byte) 0x30;
private static final byte INS_DEBIT = (byte) 0x40;
// PIN code object
private OwnerPIN pin;
private static final byte MAX_PIN_TRY = 3;
private static final byte PIN_LENGTH = 4;
// Application data
private short balance;
// Constructor - called during applet installation
public MyFirstApplet() {
pin = new OwnerPIN(MAX_PIN_TRY, PIN_LENGTH);
balance = 1000; // Initial balance
// Register this applet with the JCRE
register();
}
// Main entry point for applet installation
public static void install(byte[] bArray, short bOffset, byte bLength) {
new MyFirstApplet();
}
// Process incoming APDU commands
public void process(APDU apdu) {
byte[] buffer = apdu.getBuffer();
// SELECT APDU case - the applet is being selected
if (selectingApplet()) {
return;
}
byte cla = buffer[ISO7816.OFFSET_CLA];
byte ins = buffer[ISO7816.OFFSET_INS];
// Verify CLA (class) - often 0x80 for proprietary commands
if (cla != (byte)0x80) {
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
}
// Dispatch based on INS (instruction)
switch (ins) {
case INS_VERIFY_PIN:
verifyPin(apdu);
break;
case INS_GET_BALANCE:
getBalance(apdu);
break;
case INS_DEBIT:
debit(apdu);
break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
}
private void verifyPin(APDU apdu) {
byte[] buffer = apdu.getBuffer();
// Get the PIN data from APDU
byte lc = buffer[ISO7816.OFFSET_LC];
byte pinOffset = ISO7816.OFFSET_CDATA;
// Check PIN
if (pin.check(buffer, pinOffset, lc)) {
// PIN correct
return; // Success - no response data needed
} else {
// PIN incorrect or blocked
if (pin.getTriesRemaining() == 0) {
ISOException.throwIt((short)0x6983); // PIN blocked
} else {
ISOException.throwIt((short)0x63C0 | pin.getTriesRemaining()); // Incorrect PIN
}
}
}
private void getBalance(APDU apdu) {
// Only allowed if PIN is verified
if (!pin.isValidated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
// Set response data - balance as 2 bytes (big-endian)
Util.setShort(buffer, (short)0, balance);
// Send 2 bytes of response data
apdu.setOutgoingAndSend((short)0, (short)2);
}
private void debit(APDU apdu) {
if (!pin.isValidated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
byte lc = buffer[ISO7816.OFFSET_LC];
if (lc != 2) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Get debit amount from APDU data
short debitAmount = Util.getShort(buffer, ISO7816.OFFSET_CDATA);
// Check sufficient balance
if (balance < debitAmount) {
ISOException.throwIt((short)0x6A84); // Not enough funds
}
// Perform debit operation
balance -= debitAmount;
// Success - no response data needed
}
}

3. APDU Communication Protocol

Java Card applets communicate exclusively through APDUs, which are binary packets.

Command APDU (from terminal to card):

CLA | INS | P1 | P2 | Lc | Data | Le
  • CLA: Class byte (0x80 for proprietary commands)
  • INS: Instruction byte (your custom command)
  • P1, P2: Parameter bytes
  • Lc: Length of incoming data
  • Data: Actual data bytes (length = Lc)
  • Le: Expected length of response data

Response APDU (from card to terminal):

Data | SW1 | SW2
  • Data: Response data (optional)
  • SW1, SW2: Status words (e.g., 0x9000 = Success)

4. Development Tools and Workflow

Required Tools:

  1. Java Card Development Kit (JCKit): Provides the javacard.framework and other essential APIs.
  2. Java Card Classic Development Kit 3.1.0+: The current standard SDK.
  3. IDE: Eclipse with JCDE plugin or any Java IDE.
  4. Simulator/Emulator: JCWDE (Java Card Wireless Development Emulator) or proprietary simulators.
  5. GlobalPlatform Pro: Open-source tool for applet management on real cards.

Build Process:

# Compile with Java Card specific bootclasspath
javac -g -source 1.8 -target 1.8 \
-bootclasspath "/path/to/jckit/lib/api.jar" \
MyFirstApplet.java
# Convert to CAP file (Converted Applet)
java -jar "/path/to/jckit/bin/converter.jar" \
-classdir . \
-exportpath "/path/to/jckit/api_export_files" \
-applet 0xA0:0x00:0x00:0x00:0x62:0x03:0x01:0x0C:0x06:0x01 MyFirstApplet \
-out CAP -d . \
MyFirstApplet

5. Key Java Card APIs and Best Practices

Essential Packages:

  • javacard.framework: Core framework (Applet, APDU, PIN, etc.)
  • javacard.security: Cryptography (AES, RSA, ECC, random numbers)
  • javacardx.crypto: Extended crypto operations
  • javacardx.framework.math: Big integer support

Memory Management Best Practices:

public class MemoryEfficientApplet extends Applet {
private byte[] sharedBuffer; // Reuse buffers
public MemoryEfficientApplet() {
// Allocate all objects once during installation
sharedBuffer = new byte[256]; // Maximum expected APDU size
}
private void processData(APDU apdu) {
byte[] buffer = apdu.getBuffer(); // Use the shared APDU buffer
// Reuse the buffer for different purposes
// Never create new objects in process() method!
}
}

Security Best Practices:

  1. Always validate PIN before sensitive operations
  2. Use secure cryptographic algorithms from javacard.security
  3. Never expose sensitive data in error messages
  4. Implement secure channel protocols for external communication
  5. Use status words properly to avoid information leakage

6. Testing and Deployment

Testing with JCWDE:

// Test using the Java Card Wireless Development Emulator
// This allows testing without physical hardware
public class AppletTest {
public static void main(String[] args) throws Exception {
// Use JCWDE or proprietary testing tools
// Send test APDUs and verify responses
}
}

Sample Test APDU Sequence:

// SELECT applet
byte[] selectAPDU = {0x00, 0xA4, 0x04, 0x00, 0x0A, 
0xA0, 0x00, 0x00, 0x00, 0x62, 0x03, 0x01, 0x0C, 0x06, 0x01};
// VERIFY PIN (1234)
byte[] verifyAPDU = {(byte)0x80, 0x20, 0x00, 0x00, 0x04, 0x31, 0x32, 0x33, 0x34};
// GET BALANCE
byte[] balanceAPDU = {(byte)0x80, 0x30, 0x00, 0x00, 0x00};

Deployment to Real Card:

# Using GlobalPlatform Pro
gp.exe --install MyFirstApplet.cap \
--params 01020304 \
--applet A0:00:00:00:62:03:01:0C:06:01

7. Advanced Features

Cryptographic Operations:

public class CryptoApplet extends Applet {
private AESKey aesKey;
private Cipher cipher;
public CryptoApplet() {
// Generate AES key
aesKey = (AESKey)KeyBuilder.buildKey(
KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false);
// Generate random key data
byte[] keyData = new byte[16];
RandomData random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
random.generateData(keyData, (short)0, (short)16);
aesKey.setKey(keyData, (short)0);
// Initialize cipher
cipher = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false);
}
}

Transaction Support:

private void updateBalance(APDU apdu, short newBalance) {
// Begin transaction to ensure atomicity
JCSystem.beginTransaction();
try {
balance = newBalance;
// Other related updates...
JCSystem.commitTransaction();
} catch (Exception e) {
JCSystem.abortTransaction();
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
}

Conclusion

Java Card applet development requires a paradigm shift from traditional Java programming. The constraints demand careful memory management, security-first design, and efficient algorithm implementation. However, the ability to create secure, portable applications that run on billions of devices worldwide makes it a valuable skill for security and IoT developers.

The key to success is understanding the lifecycle constraints, mastering APDU communication, and rigorously testing on both simulators and real hardware. While the learning curve is steep, the Java Card platform remains the gold standard for secure element programming.


Further Reading:

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper