google-site-verification: google61fe8ba583a51912.html
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