Java Card for Smart Cards in Java

Overview

Java Card is a technology that allows Java-based applications to run on smart cards and other devices with limited memory and processing capabilities. It's widely used in SIM cards, payment cards, ID cards, and secure access systems.

Java Card Platform Architecture

1. Java Card Runtime Environment (JCRE)

// Basic Java Card applet structure
package com.example;
import javacard.framework.*;
import javacard.security.*;
import javacardx.crypto.*;
public class BasicApplet extends Applet {
// Constants
private static final byte CLA_BASIC = (byte)0x80;
private static final byte INS_SELECT = (byte)0xA4;
private static final byte INS_GET_DATA = (byte)0xCA;
private static final byte INS_STORE_DATA = (byte)0xDA;
private static final byte INS_VERIFY_PIN = (byte)0x20;
private static final byte INS_CHANGE_PIN = (byte)0x24;
// PIN code
private OwnerPIN pin;
private static final byte PIN_TRY_LIMIT = (byte)3;
private static final byte PIN_SIZE = (byte)4;
// Data storage
private byte[] storedData;
private static final short DATA_SIZE = (short)256;
// Cryptographic objects
private AESKey aesKey;
private Cipher aesCipher;
public static void install(byte[] bArray, short bOffset, byte bLength) {
new BasicApplet().register(bArray, (short)(bOffset + 1), bArray[bOffset]);
}
public BasicApplet() {
// Initialize PIN
pin = new OwnerPIN(PIN_TRY_LIMIT, PIN_SIZE);
// Initialize data storage
storedData = new byte[DATA_SIZE];
// Initialize cryptographic objects
initializeCrypto();
register();
}
private void initializeCrypto() {
try {
aesKey = (AESKey)KeyBuilder.buildKey(
KeyBuilder.TYPE_AES, 
KeyBuilder.LENGTH_AES_128, 
false
);
aesCipher = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false);
} catch (CryptoException e) {
// Handle crypto initialization error
}
}
public boolean select() {
// Reset PIN validation on applet selection
pin.reset();
return true;
}
public void deselect() {
// Reset PIN when applet is deselected
pin.reset();
}
public void process(APDU apdu) {
byte[] buffer = apdu.getBuffer();
// Check for SELECT command
if (selectingApplet()) {
return;
}
// Check CLA (Class byte)
if (buffer[ISO7816.OFFSET_CLA] != CLA_BASIC) {
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
}
// Process commands based on INS (Instruction byte)
switch (buffer[ISO7816.OFFSET_INS]) {
case INS_VERIFY_PIN:
verifyPIN(apdu);
break;
case INS_CHANGE_PIN:
changePIN(apdu);
break;
case INS_STORE_DATA:
storeData(apdu);
break;
case INS_GET_DATA:
getData(apdu);
break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
}
private void verifyPIN(APDU apdu) {
byte[] buffer = apdu.getBuffer();
// Get PIN data from APDU
short lc = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF);
short dataOffset = ISO7816.OFFSET_CDATA;
// Check PIN
if (pin.check(buffer, dataOffset, (byte)lc)) {
// PIN verified successfully
apdu.setOutgoingAndSend((short)0, (short)0);
} else {
// PIN verification failed
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
}
private void changePIN(APDU apdu) {
if (!pin.isValidated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
short lc = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF);
short dataOffset = ISO7816.OFFSET_CDATA;
// Update PIN
pin.update(buffer, dataOffset, (byte)lc);
apdu.setOutgoingAndSend((short)0, (short)0);
}
private void storeData(APDU apdu) {
if (!pin.isValidated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
short lc = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF);
short dataOffset = ISO7816.OFFSET_CDATA;
// Store data (with bounds checking)
if (lc > DATA_SIZE) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
Util.arrayCopyNonAtomic(buffer, dataOffset, storedData, (short)0, lc);
apdu.setOutgoingAndSend((short)0, (short)0);
}
private void getData(APDU apdu) {
if (!pin.isValidated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
short le = apdu.setOutgoing();
if (le > DATA_SIZE) {
le = DATA_SIZE;
}
// Send stored data
apdu.setOutgoingLength(le);
apdu.sendBytesLong(storedData, (short)0, le);
}
}

Cryptographic Applet Example

2. Secure Cryptographic Applet

package com.securecrypto;
import javacard.framework.*;
import javacard.security.*;
import javacardx.crypto.*;
public class CryptoApplet extends Applet {
// Command constants
private static final byte CLA_CRYPTO = (byte)0x84;
private static final byte INS_GENERATE_KEY = (byte)0x40;
private static final byte INS_ENCRYPT = (byte)0x42;
private static final byte INS_DECRYPT = (byte)0x44;
private static final byte INS_SIGN = (byte)0x46;
private static final byte INS_VERIFY = (byte)0x48;
private static final byte INS_GENERATE_RANDOM = (byte)0x4A;
// PIN management
private OwnerPIN adminPIN;
private static final byte PIN_TRY_LIMIT = (byte)3;
private static final byte PIN_SIZE = (byte)6;
// Cryptographic keys
private AESKey aesKey;
private RSAPrivateKey rsaPrivateKey;
private RSAPublicKey rsaPublicKey;
private ECPrivateKey ecPrivateKey;
private ECPublicKey ecPublicKey;
// Ciphers and signatures
private Cipher aesCipher;
private Signature rsaSignature;
private Signature ecdsaSignature;
// Key buffers
private byte[] tempBuffer;
private static final short TEMP_BUFFER_SIZE = (short)512;
public static void install(byte[] bArray, short bOffset, byte bLength) {
new CryptoApplet().register();
}
public CryptoApplet() {
// Initialize PIN
adminPIN = new OwnerPIN(PIN_TRY_LIMIT, PIN_SIZE);
// Initialize temporary buffer
tempBuffer = new byte[TEMP_BUFFER_SIZE];
// Initialize cryptographic objects
initializeCryptoObjects();
register();
}
private void initializeCryptoObjects() {
try {
// Initialize AES
aesKey = (AESKey)KeyBuilder.buildKey(
KeyBuilder.TYPE_AES, 
KeyBuilder.LENGTH_AES_256, 
false
);
aesCipher = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false);
// Initialize RSA
rsaPrivateKey = (RSAPrivateKey)KeyBuilder.buildKey(
KeyBuilder.TYPE_RSA_PRIVATE, 
KeyBuilder.LENGTH_RSA_2048, 
false
);
rsaPublicKey = (RSAPublicKey)KeyBuilder.buildKey(
KeyBuilder.TYPE_RSA_PUBLIC, 
KeyBuilder.LENGTH_RSA_2048, 
false
);
rsaSignature = Signature.getInstance(Signature.ALG_RSA_SHA_256_PKCS1, false);
// Initialize ECC
ecPrivateKey = (ECPrivateKey)KeyBuilder.buildKey(
KeyBuilder.TYPE_EC_FP_PRIVATE, 
KeyBuilder.LENGTH_EC_FP_256, 
false
);
ecPublicKey = (ECPublicKey)KeyBuilder.buildKey(
KeyBuilder.TYPE_EC_FP_PUBLIC, 
KeyBuilder.LENGTH_EC_FP_256, 
false
);
ecdsaSignature = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false);
} catch (CryptoException e) {
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
}
public void process(APDU apdu) {
byte[] buffer = apdu.getBuffer();
if (selectingApplet()) {
return;
}
if (buffer[ISO7816.OFFSET_CLA] != CLA_CRYPTO) {
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
}
// Check PIN for all operations except PIN verification
if (buffer[ISO7816.OFFSET_INS] != INS_VERIFY_PIN && !adminPIN.isValidated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
switch (buffer[ISO7816.OFFSET_INS]) {
case INS_VERIFY_PIN:
verifyPIN(apdu);
break;
case INS_GENERATE_KEY:
generateKey(apdu);
break;
case INS_ENCRYPT:
encryptData(apdu);
break;
case INS_DECRYPT:
decryptData(apdu);
break;
case INS_SIGN:
signData(apdu);
break;
case INS_VERIFY:
verifySignature(apdu);
break;
case INS_GENERATE_RANDOM:
generateRandom(apdu);
break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
}
private void verifyPIN(APDU apdu) {
byte[] buffer = apdu.getBuffer();
short lc = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF);
if (adminPIN.check(buffer, ISO7816.OFFSET_CDATA, (byte)lc)) {
apdu.setOutgoingAndSend((short)0, (short)0);
} else {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
}
private void generateKey(APDU apdu) {
byte[] buffer = apdu.getBuffer();
byte keyType = buffer[ISO7816.OFFSET_P1]; // P1 parameter specifies key type
try {
switch (keyType) {
case 0x01: // AES
// Generate random AES key
RandomData random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
random.generateData(tempBuffer, (short)0, (short)32); // 256-bit key
aesKey.setKey(tempBuffer, (short)0);
break;
case 0x02: // RSA
// Generate RSA key pair
KeyPair rsaPair = new KeyPair(KeyPair.ALG_RSA, KeyBuilder.LENGTH_RSA_2048);
rsaPair.genKeyPair();
rsaPrivateKey = (RSAPrivateKey)rsaPair.getPrivate();
rsaPublicKey = (RSAPublicKey)rsaPair.getPublic();
break;
case 0x03: // ECC
// Generate ECC key pair
KeyPair eccPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256);
eccPair.genKeyPair();
ecPrivateKey = (ECPrivateKey)eccPair.getPrivate();
ecPublicKey = (ECPublicKey)eccPair.getPublic();
break;
default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
apdu.setOutgoingAndSend((short)0, (short)0);
} catch (CryptoException e) {
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
}
private void encryptData(APDU apdu) {
byte[] buffer = apdu.getBuffer();
byte algorithm = buffer[ISO7816.OFFSET_P1];
short lc = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF);
try {
short encryptedLength;
switch (algorithm) {
case 0x01: // AES
aesCipher.init(aesKey, Cipher.MODE_ENCRYPT);
encryptedLength = aesCipher.doFinal(
buffer, ISO7816.OFFSET_CDATA, lc, 
tempBuffer, (short)0
);
break;
case 0x02: // RSA
rsaSignature.init(rsaPrivateKey, Signature.MODE_SIGN);
encryptedLength = rsaSignature.sign(
buffer, ISO7816.OFFSET_CDATA, lc, 
tempBuffer, (short)0
);
break;
default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
return;
}
// Send encrypted data back
apdu.setOutgoing();
apdu.setOutgoingLength(encryptedLength);
apdu.sendBytesLong(tempBuffer, (short)0, encryptedLength);
} catch (CryptoException e) {
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
}
private void decryptData(APDU apdu) {
byte[] buffer = apdu.getBuffer();
byte algorithm = buffer[ISO7816.OFFSET_P1];
short lc = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF);
try {
short decryptedLength;
switch (algorithm) {
case 0x01: // AES
aesCipher.init(aesKey, Cipher.MODE_DECRYPT);
decryptedLength = aesCipher.doFinal(
buffer, ISO7816.OFFSET_CDATA, lc, 
tempBuffer, (short)0
);
break;
case 0x02: // RSA
rsaSignature.init(rsaPublicKey, Signature.MODE_VERIFY);
boolean verified = rsaSignature.verify(
buffer, ISO7816.OFFSET_CDATA, lc, 
tempBuffer, (short)0, (short)256
);
if (!verified) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
return;
}
decryptedLength = lc;
break;
default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
return;
}
// Send decrypted data back
apdu.setOutgoing();
apdu.setOutgoingLength(decryptedLength);
apdu.sendBytesLong(tempBuffer, (short)0, decryptedLength);
} catch (CryptoException e) {
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
}
private void signData(APDU apdu) {
byte[] buffer = apdu.getBuffer();
byte algorithm = buffer[ISO7816.OFFSET_P1];
short lc = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF);
try {
short signatureLength;
switch (algorithm) {
case 0x01: // RSA
rsaSignature.init(rsaPrivateKey, Signature.MODE_SIGN);
signatureLength = rsaSignature.sign(
buffer, ISO7816.OFFSET_CDATA, lc, 
tempBuffer, (short)0
);
break;
case 0x02: // ECDSA
ecdsaSignature.init(ecPrivateKey, Signature.MODE_SIGN);
signatureLength = ecdsaSignature.sign(
buffer, ISO7816.OFFSET_CDATA, lc, 
tempBuffer, (short)0
);
break;
default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
return;
}
// Send signature back
apdu.setOutgoing();
apdu.setOutgoingLength(signatureLength);
apdu.sendBytesLong(tempBuffer, (short)0, signatureLength);
} catch (CryptoException e) {
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
}
private void verifySignature(APDU apdu) {
byte[] buffer = apdu.getBuffer();
byte algorithm = buffer[ISO7816.OFFSET_P1];
short lc = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF);
// For signature verification, data comes in two parts
short dataLength = (short)(buffer[ISO7816.OFFSET_P2] & 0xFF);
short signatureOffset = (short)(ISO7816.OFFSET_CDATA + dataLength);
short signatureLength = (short)(lc - dataLength);
try {
boolean verified;
switch (algorithm) {
case 0x01: // RSA
rsaSignature.init(rsaPublicKey, Signature.MODE_VERIFY);
verified = rsaSignature.verify(
buffer, ISO7816.OFFSET_CDATA, dataLength, 
buffer, signatureOffset, signatureLength
);
break;
case 0x02: // ECDSA
ecdsaSignature.init(ecPublicKey, Signature.MODE_VERIFY);
verified = ecdsaSignature.verify(
buffer, ISO7816.OFFSET_CDATA, dataLength, 
buffer, signatureOffset, signatureLength
);
break;
default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
return;
}
if (verified) {
apdu.setOutgoingAndSend((short)0, (short)0);
} else {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
} catch (CryptoException e) {
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
}
private void generateRandom(APDU apdu) {
byte[] buffer = apdu.getBuffer();
short le = apdu.setOutgoing();
try {
RandomData random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
random.generateData(tempBuffer, (short)0, le);
apdu.setOutgoingLength(le);
apdu.sendBytesLong(tempBuffer, (short)0, le);
} catch (CryptoException e) {
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
}
}

Payment Application Example

3. EMV-like Payment Applet

package com.payment;
import javacard.framework.*;
import javacard.security.*;
import javacardx.crypto.*;
public class PaymentApplet extends Applet {
// Command constants
private static final byte CLA_PAYMENT = (byte)0x80;
private static final byte INS_INITIALIZE = (byte)0x50;
private static final byte INS_VERIFY_PIN = (byte)0x20;
private static final byte INS_CHANGE_PIN = (byte)0x24;
private static final byte INS_GET_BALANCE = (byte)0x5C;
private static final byte INS_DEBIT = (byte)0x5A;
private static final byte INS_CREDIT = (byte)0x52;
private static final byte INS_GET_TRANSACTION_LOG = (byte)0x5E;
// PIN management
private OwnerPIN pin;
private static final byte PIN_TRY_LIMIT = (byte)3;
private static final byte PIN_SIZE = (byte)4;
// Account data
private short balance;
private short dailyLimit;
private short dailySpent;
private byte[] lastTransactionDate;
// Transaction log
private byte[] transactionLog;
private short logPointer;
private static final short LOG_SIZE = (short)10;
private static final short LOG_ENTRY_SIZE = (short)8;
// Cryptographic objects
private DESKey desKey;
private Cipher desCipher;
// Card data
private byte[] cardNumber;
private byte[] cardHolderName;
private byte[] expiryDate;
public static void install(byte[] bArray, short bOffset, byte bLength) {
new PaymentApplet().register();
}
public PaymentApplet() {
// Initialize PIN
pin = new OwnerPIN(PIN_TRY_LIMIT, PIN_SIZE);
// Initialize account data
balance = (short)0;
dailyLimit = (short)10000; // $100.00
dailySpent = (short)0;
lastTransactionDate = new byte[3]; // YYMMDD
// Initialize transaction log
transactionLog = new byte[LOG_SIZE * LOG_ENTRY_SIZE];
logPointer = (short)0;
// Initialize card data
cardNumber = new byte[16];
cardHolderName = new byte[26];
expiryDate = new byte[2];
// Initialize cryptography
initializeCrypto();
register();
}
private void initializeCrypto() {
try {
desKey = (DESKey)KeyBuilder.buildKey(
KeyBuilder.TYPE_DES, 
KeyBuilder.LENGTH_DES3_3KEY, 
false
);
desCipher = Cipher.getInstance(Cipher.ALG_DES_CBC_NOPAD, false);
} catch (CryptoException e) {
// Handle initialization error
}
}
public void process(APDU apdu) {
byte[] buffer = apdu.getBuffer();
if (selectingApplet()) {
return;
}
if (buffer[ISO7816.OFFSET_CLA] != CLA_PAYMENT) {
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
}
switch (buffer[ISO7816.OFFSET_INS]) {
case INS_VERIFY_PIN:
verifyPIN(apdu);
break;
case INS_CHANGE_PIN:
changePIN(apdu);
break;
case INS_INITIALIZE:
initializeCard(apdu);
break;
case INS_GET_BALANCE:
getBalance(apdu);
break;
case INS_DEBIT:
debit(apdu);
break;
case INS_CREDIT:
credit(apdu);
break;
case INS_GET_TRANSACTION_LOG:
getTransactionLog(apdu);
break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
}
private void verifyPIN(APDU apdu) {
byte[] buffer = apdu.getBuffer();
short lc = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF);
if (pin.check(buffer, ISO7816.OFFSET_CDATA, (byte)lc)) {
apdu.setOutgoingAndSend((short)0, (short)0);
} else {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
}
private void changePIN(APDU apdu) {
if (!pin.isValidated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
short lc = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF);
pin.update(buffer, ISO7816.OFFSET_CDATA, (byte)lc);
apdu.setOutgoingAndSend((short)0, (short)0);
}
private void initializeCard(APDU apdu) {
if (!pin.isValidated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
short lc = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF);
short offset = ISO7816.OFFSET_CDATA;
// Parse initialization data
// Structure: [cardNumber(16)][cardHolderName(26)][expiryDate(2)][initialBalance(2)]
// Card number
Util.arrayCopyNonAtomic(buffer, offset, cardNumber, (short)0, (short)16);
offset += 16;
// Card holder name
Util.arrayCopyNonAtomic(buffer, offset, cardHolderName, (short)0, (short)26);
offset += 26;
// Expiry date
Util.arrayCopyNonAtomic(buffer, offset, expiryDate, (short)0, (short)2);
offset += 2;
// Initial balance
balance = (short)((buffer[offset] << 8) | (buffer[offset + 1] & 0xFF));
apdu.setOutgoingAndSend((short)0, (short)0);
}
private void getBalance(APDU apdu) {
if (!pin.isValidated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
// Return balance (2 bytes)
buffer[0] = (byte)((balance >> 8) & 0xFF);
buffer[1] = (byte)(balance & 0xFF);
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();
short lc = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF);
if (lc != 2) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Parse amount (2 bytes)
short amount = (short)((buffer[ISO7816.OFFSET_CDATA] << 8) | 
(buffer[ISO7816.OFFSET_CDATA + 1] & 0xFF));
// Check balance
if (amount > balance) {
ISOException.throwIt((short)0x6A85); // Insufficient funds
}
// Check daily limit
if ((dailySpent + amount) > dailyLimit) {
ISOException.throwIt((short)0x6A86); // Daily limit exceeded
}
// Perform debit
balance -= amount;
dailySpent += amount;
// Log transaction
logTransaction((byte)0x01, amount); // 0x01 = debit
apdu.setOutgoingAndSend((short)0, (short)0);
}
private void credit(APDU apdu) {
if (!pin.isValidated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
short lc = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF);
if (lc != 2) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Parse amount (2 bytes)
short amount = (short)((buffer[ISO7816.OFFSET_CDATA] << 8) | 
(buffer[ISO7816.OFFSET_CDATA + 1] & 0xFF));
// Check for overflow
if ((Short.MAX_VALUE - balance) < amount) {
ISOException.throwIt((short)0x6A86); // Amount too large
}
// Perform credit
balance += amount;
// Log transaction
logTransaction((byte)0x02, amount); // 0x02 = credit
apdu.setOutgoingAndSend((short)0, (short)0);
}
private void getTransactionLog(APDU apdu) {
if (!pin.isValidated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
short le = apdu.setOutgoing();
// Calculate how many log entries to return
short entriesToReturn = (short)(le / LOG_ENTRY_SIZE);
if (entriesToReturn > LOG_SIZE) {
entriesToReturn = LOG_SIZE;
}
// Copy log entries to buffer
short bytesToSend = (short)(entriesToReturn * LOG_ENTRY_SIZE);
Util.arrayCopyNonAtomic(transactionLog, (short)0, buffer, (short)0, bytesToSend);
apdu.setOutgoingLength(bytesToSend);
apdu.sendBytes((short)0, bytesToSend);
}
private void logTransaction(byte type, short amount) {
// Get current date (simplified - in real app, use secure clock)
byte[] currentDate = getCurrentDate();
// Calculate log entry offset
short offset = (short)(logPointer * LOG_ENTRY_SIZE);
// Store transaction type
transactionLog[offset] = type;
offset++;
// Store amount (2 bytes)
transactionLog[offset] = (byte)((amount >> 8) & 0xFF);
transactionLog[offset + 1] = (byte)(amount & 0xFF);
offset += 2;
// Store date (3 bytes - YYMMDD)
Util.arrayCopyNonAtomic(currentDate, (short)0, transactionLog, offset, (short)3);
// Update log pointer (circular buffer)
logPointer = (short)((logPointer + 1) % LOG_SIZE);
}
private byte[] getCurrentDate() {
// In a real application, this would get the date from a secure clock
// For demonstration, return a fixed date
byte[] date = new byte[3];
date[0] = (byte)0x18; // Year
date[1] = (byte)0x01; // Month
date[2] = (byte)0x01; // Day
return date;
}
// Reset daily spent amount (should be called once per day)
private void resetDailySpent() {
dailySpent = (short)0;
}
}

Development Tools and Workflow

4. Java Card Development Setup

// Build script example (Ant build.xml)
/*
<project name="JavaCardApplet" default="build">
<property name="jckit.dir" value="/path/to/javacard-kit"/>
<property name="applet.aid" value="0x01:0x02:0x03:0x04:0x05:0x06:0x07"/>
<target name="build">
<javac srcdir="src" destdir="bin" 
bootclasspath="${jckit.dir}/lib/api.jar"
target="1.1" source="1.1"/>
<convert verbose="true"
export="true"
classdir="bin"
jca="true"
applet="0xa0:0x00:0x00:0x00:0x62:0x03:0x01:0x0c:0x06:0x01:${applet.aid}"
i="MyApplet.cap">
<cap source="bin" package="com.example.MyApplet" />
</convert>
</target>
</project>
*/
// Testing framework example
package com.test;
import javacard.framework.*;
import javacard.security.*;
public class TestApplet extends Applet {
private static final byte CLA_TEST = (byte)0x80;
private static final byte INS_RUN_TESTS = (byte)0x70;
public static void install(byte[] bArray, short bOffset, byte bLength) {
new TestApplet().register();
}
public void process(APDU apdu) {
byte[] buffer = apdu.getBuffer();
if (selectingApplet()) {
return;
}
if (buffer[ISO7816.OFFSET_CLA] != CLA_TEST) {
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
}
switch (buffer[ISO7816.OFFSET_INS]) {
case INS_RUN_TESTS:
runTests(apdu);
break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
}
private void runTests(APDU apdu) {
byte[] buffer = apdu.getBuffer();
short testResults = 0;
// Run various tests
if (testMemory()) testResults |= 0x01;
if (testCrypto()) testResults |= 0x02;
if (testPIN()) testResults |= 0x04;
// Return test results
buffer[0] = (byte)(testResults & 0xFF);
apdu.setOutgoingAndSend((short)0, (short)1);
}
private boolean testMemory() {
try {
byte[] testArray = new byte[100];
Util.arrayFillNonAtomic(testArray, (short)0, (short)100, (byte)0xAA);
for (short i = 0; i < 100; i++) {
if (testArray[i] != (byte)0xAA) {
return false;
}
}
return true;
} catch (Exception e) {
return false;
}
}
private boolean testCrypto() {
try {
RandomData random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
byte[] randomBytes = new byte[16];
random.generateData(randomBytes, (short)0, (short)16);
return true;
} catch (Exception e) {
return false;
}
}
private boolean testPIN() {
try {
OwnerPIN testPIN = new OwnerPIN((byte)3, (byte)4);
byte[] testPinBytes = {(byte)0x12, (byte)0x34, (byte)0x56, (byte)0x78};
testPIN.update(testPinBytes, (short)0, (byte)4);
return testPIN.check(testPinBytes, (short)0, (byte)4);
} catch (Exception e) {
return false;
}
}
}

Best Practices and Security Considerations

5. Security Guidelines

package com.secure;
import javacard.framework.*;
import javacard.security.*;
public class SecureApplet extends Applet {
// Security best practices implementation
// 1. Use secure random number generation
private RandomData secureRandom;
// 2. Implement proper PIN management
private OwnerPIN pin;
private static final byte PIN_TRY_LIMIT = (byte)3;
// 3. Secure key storage
private AESKey masterKey;
// 4. Transaction counters
private short transactionCounter;
private short maxTransactionsPerSession = (short)10;
// 5. Secure memory management
private byte[] secureBuffer;
private static final short SECURE_BUFFER_SIZE = (short)256;
public SecureApplet() {
// Initialize security components
initializeSecurity();
}
private void initializeSecurity() {
try {
// Initialize secure random
secureRandom = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
// Initialize PIN
pin = new OwnerPIN(PIN_TRY_LIMIT, (byte)6);
// Initialize master key
masterKey = (AESKey)KeyBuilder.buildKey(
KeyBuilder.TYPE_AES, 
KeyBuilder.LENGTH_AES_256, 
false
);
// Generate random key
byte[] keyData = new byte[32];
secureRandom.generateData(keyData, (short)0, (short)32);
masterKey.setKey(keyData, (short)0);
// Initialize secure buffer
secureBuffer = new byte[SECURE_BUFFER_SIZE];
// Clear sensitive data from memory
Util.arrayFillNonAtomic(keyData, (short)0, (short)32, (byte)0);
} catch (Exception e) {
// Handle initialization errors securely
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
}
// Secure data processing
private void processSecureData(APDU apdu) {
if (!pin.isValidated()) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
// Check transaction counter
if (transactionCounter >= maxTransactionsPerSession) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
byte[] buffer = apdu.getBuffer();
// Secure data processing with bounds checking
short dataLength = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF);
if (dataLength > SECURE_BUFFER_SIZE) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Copy data to secure buffer
Util.arrayCopyNonAtomic(buffer, ISO7816.OFFSET_CDATA, 
secureBuffer, (short)0, dataLength);
// Process data...
// Clear secure buffer after use
Util.arrayFillNonAtomic(secureBuffer, (short)0, dataLength, (byte)0);
// Increment transaction counter
transactionCounter++;
}
// Secure key generation
private void generateSecureKey() {
try {
// Generate key using secure random
byte[] keyMaterial = new byte[32];
secureRandom.generateData(keyMaterial, (short)0, (short)32);
// Set the key
masterKey.setKey(keyMaterial, (short)0);
// Clear key material from memory
Util.arrayFillNonAtomic(keyMaterial, (short)0, (short)32, (byte)0);
} catch (CryptoException e) {
ISOException.throwIt(ISO7816.SW_UNKNOWN);
}
}
// Secure memory cleanup
public void deselect() {
// Clear sensitive data when applet is deselected
if (secureBuffer != null) {
Util.arrayFillNonAtomic(secureBuffer, (short)0, SECURE_BUFFER_SIZE, (byte)0);
}
// Reset transaction counter
transactionCounter = (short)0;
// Reset PIN validation
pin.reset();
}
}

Key Java Card Features

  1. Limited Environment: Optimized for small memory footprint
  2. Security: Built-in cryptographic operations and secure element
  3. APDU Communication: ISO 7816 standard communication protocol
  4. Persistent Objects: Data persists across sessions
  5. Transaction Support: Atomic operations for data integrity
  6. GlobalPlatform: Standard for card management

Common Use Cases

  • Payment Systems: Credit/debit cards, e-wallets
  • Identification: National ID cards, employee badges
  • Mobile Communications: SIM cards
  • Access Control: Secure building access
  • Healthcare: Health insurance cards

Java Card technology enables secure, portable applications that can run on resource-constrained devices while maintaining high security standards.

Leave a Reply

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


Macro Nepal Helper