疫情原因,智能卡安全课程也只能在家听听ppt,然后根据实验指导书做一下实验,实验还是比较有意思的,我们可以通过编写Java代码在智能卡模拟平台上模拟若干应用。课程实验内容需要我们做一个模拟电子钱包并添加安全认证等内容的应用。
其中程序代码部分和测试部分需要一定的Java智能卡基础知识,如Applet命令格式等内容,建议没有基础知识的朋友先学习一些基础知识后再阅读本blog。
因为这是一个一系列的实验,最后综合成一个大实验,所以实验内容是一些渐进的内容,下面代码部分是最后的综合代码,仅供参考。

0x00 实验目的

开发带有内部认证、外部认证、PIN校验、MAC机制的电子钱包应用。并且使其具有事务处理能力、交易明细存储和跨应用防火墙的积分功能。

0x01 实验环境

系统环境:MAC OS Catalina 10.15.3
虚拟机环境:Parallels desktop 15+Windows 10
编程语言:Java语言
开发及运行环境:Eclipse 集成开发环境,Java Key,JCOP仿真运行环境

0x02 实验内容

1、使用Java智能卡提供的OwnerPIN类实现的PIN校验功能。
2、实现简单的电子钱包,包括加钱、减钱和余额查询功能。要求在进行交易(加钱或减钱)之前,必须进行PIN校验,只有PIN校验成功后,才能对余额数据进行修改。同时在取消选择应用时,将PIN的校验结果清空,从而要求在每次选择应用后都需进行PIN验证,避免了安全漏洞的产生。
3、在简单电子钱包的基础上增加事务处理功能。
4、在简单电子钱包的基础上增加交易记录功能。
5、再创建一个积分应用,并为电子钱包实现跨应用积分功能
6、要求钱包交易具有内、外部认证功能
7、要求钱包需对加减钱的数据进行MAC校验。

0x03 实验代码

代码部分分为两个package,purse包中是电子钱包应用,sampleLoyalty包中是积分应用。

1、purse package

先看purse包中的内容,其中有两个.java文件。Purse.java是电子钱包应用的主程序。主要包括处理电子钱包中的行为,如认证,存取款,记录交易等内容。
Purse.java:

package purse;

import javacard.framework.AID;
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.OwnerPIN;
import javacard.framework.Util;
import sampleLoyalty.JavaLoyaltyInterface;
import javacard.security.*;
import javacardx.crypto.*;

public class Purse extends Applet {
    // CLA
    final static byte Wallet_CLA = (byte) 0x80;
    final static byte VERIFY = (byte) 0x20; // 验证
    final static byte CREDIT = (byte) 0x30; // 存款
    final static byte DEBIT = (byte) 0x40; // 取款
    final static byte GET_BALANCE = (byte) 0x50; // 读取余额
    final static byte READ_FILE = (byte) 0xB2; // 读取记录文件
    final static byte IN_AUT = (byte) 0x88; // 内部认证
    final static byte OUT_RANDOM = (byte) 0x84; // 外部认证随机数
    final static byte OUT_AUT = (byte) 0x82; // 外部认证
    final static byte MTEST = (byte) 0x70; // mac签名

    // 单重DES密钥
    private byte[] keyData = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };

    // 最大余额
    final static short MAX_BALANCE = 0x7FFF;
    // 交易额最大值
    final static byte MAX_TRANSACTION_AMOUNT = 127;

    // PIN最多尝试次数
    final static byte PIN_TRY_LIMIT = (byte) 0x03;
    // PIN最大长度
    final static byte MAX_PIN_SIZE = (byte) 0x08;
    // signal that the PIN verification failed
    final static short SW_VERIFICATION_FAILED = 0x6300;
    // signal the the PIN validation is required
    // for a credit or a debit transaction
    final static short SW_PIN_VERIFICATION_REQUIRED = 0x6301;
    // signal invalid transaction amount
    // amount > MAX_TRANSACTION_AMOUNT or amount < 0
    final static short SW_INVALID_TRANSACTION_AMOUNT = 0x6A83;
    // signal that the balance exceed the maximum
    final static short SW_EXCEED_MAXIMUM_BALANCE = 0x6A84;
    // signal the the balance becomes negative
    final static short SW_NEGATIVE_BALANCE = 0x6A85;
    // 积分Applet的AID:223344556601
    byte[] loyaltyAIDValue = { (byte) 0x22, (byte) 0x33, (byte) 0x44,
            (byte) 0x55, (byte) 0x66, (byte) 0x01 };

    byte[] AppletID = { (byte) 0x11, (byte) 0x22, (byte) 0x33,
            (byte) 0x44, (byte) 0x55, (byte) 0x01 };
    
    byte[] sigbuf;  //签名数组
    
    byte[] tmp; //验证数据临时数组
    OwnerPIN pin;
    short balance;
    CyclicFile record; // 记录文件
    private DESKey indeskey; // 内部认证密钥
    Cipher inCipherObj; // 内部认证加密对象
    byte[] Random; // 外部认证随机数
    private DESKey outdeskey; // 外部认证密钥
    Cipher outCipherObj; // 外部认证加密对象
    private DESKey mackey;  //mac密钥
    Signature sig;   //mac签名对象
    Signature sig1;   //mac签名对象


    private Purse(byte[] bArray, short bOffset, byte bLength) {

        byte pinInitValue[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
        pin = new OwnerPIN(PIN_TRY_LIMIT, MAX_PIN_SIZE);
        pin.update(pinInitValue, (short) 0, (byte) 6);
        record = new CyclicFile((short) 14, (short) 5); // 每条记录长度14,最多5条记录
        // 生成内部认证密钥对象
        indeskey = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES,KeyBuilder.LENGTH_DES, false);
        // 生成内部认证加密对象
        inCipherObj = Cipher.getInstance(Cipher.ALG_DES_CBC_ISO9797_M2, false);
        //生成mac密钥对象
        mackey = (DESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES, false);                    
        //设置mac密钥                    
        mackey.setKey(keyData, (short)0);
        //初始化签名对象
        sig = Signature.getInstance(Signature.ALG_DES_MAC8_ISO9797_M2, false);
        sig1 = Signature.getInstance(Signature.ALG_DES_MAC8_ISO9797_M2, false);
        tmp = JCSystem.makeTransientByteArray((short)30,JCSystem.CLEAR_ON_DESELECT);
        sigbuf = JCSystem.makeTransientByteArray((short)8,JCSystem.CLEAR_ON_DESELECT);

        register();
    }

    public static void install(byte[] bArray, short bOffset, byte bLength) {

        new Purse(bArray, bOffset, bLength);
    }

    public boolean select() {

        // 判断是否验证
        if (pin.getTriesRemaining() == 0)
            return false;
        else
            return true;

    }

    public void deselect() {

        pin.reset();

    }

    public void process(APDU apdu) {

        byte[] buffer = apdu.getBuffer();

        buffer[ISO7816.OFFSET_CLA] = (byte) (buffer[ISO7816.OFFSET_CLA] & (byte) 0xFC);

        if ((buffer[ISO7816.OFFSET_CLA] == 0)
                && (buffer[ISO7816.OFFSET_INS] == (byte) (0xA4)))
            return;

        if (buffer[ISO7816.OFFSET_CLA] != Wallet_CLA)
            ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);

        switch (buffer[ISO7816.OFFSET_INS]) {
        case GET_BALANCE:
            getBalance(apdu);
            return;
        case DEBIT:
            debit(apdu);
            return;
        case CREDIT:
            credit(apdu);
            return;
        case VERIFY:
            verify(apdu);
            return;
        case READ_FILE:
            ReadFile(apdu);
            return;
        case IN_AUT:
            indoAuthentication(apdu);
            return;
        case OUT_RANDOM:
            getRandom(apdu);
            return;
        case OUT_AUT:
            outdoAuthentication(apdu);
            return;
        case MTEST:
            GenerateSignature(apdu);
            return;
        default:
            ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
        }

    }

    private void credit(APDU apdu) {
        // 判断许可
        if (!pin.isValidated())
            ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
        // 启动事务
        JCSystem.beginTransaction();
        byte[] buffer = apdu.getBuffer();
        byte numBytes = buffer[ISO7816.OFFSET_LC];
        byte byteRead = (byte) (apdu.setIncomingAndReceive());

        // 获取交易额
        byte creditAmount = buffer[ISO7816.OFFSET_CDATA];
        // 设置校验数据
        tmp[0] = creditAmount;
        tmp[1] = (byte)0xFE; //存款标识符0xFE
        Util.arrayCopy(AppletID,(short)0,tmp, (short)2, (short)6); // 加入AID
        // 签名
        Signature(tmp);
        // 验签
        if (!VerifySignature(buffer))
        {
            JCSystem.abortTransaction();
            ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
        }
        // 判断交易额
        if ((creditAmount > MAX_TRANSACTION_AMOUNT) || (creditAmount < 0)) {
            // 终止事务
            JCSystem.abortTransaction();
            ISOException.throwIt(SW_INVALID_TRANSACTION_AMOUNT);
        }
        // 检查新的余额
        if ((short) (balance + creditAmount) > MAX_BALANCE) {
            // 终止事务
            JCSystem.abortTransaction();
            ISOException.throwIt(SW_EXCEED_MAXIMUM_BALANCE);
        }
        // 更新
        balance = (short) (balance + creditAmount);
        // 添加记录
        record.AppendRecord(buffer, record.recordsize);
        // 更新积分
        grantPoints(creditAmount);
        // 提交事务
        JCSystem.commitTransaction();

    } // end of deposit method

    private void debit(APDU apdu) {
        // 同上
        if (!pin.isValidated())
            ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
        // 启动事务
        JCSystem.beginTransaction();

        byte[] buffer = apdu.getBuffer();
        byte numBytes = (byte) (buffer[ISO7816.OFFSET_LC]);
        byte byteRead = (byte) (apdu.setIncomingAndReceive());

        byte debitAmount = buffer[ISO7816.OFFSET_CDATA];
        // 设置校验数据
        tmp[0] = debitAmount;
        tmp[1] = (byte)0xFF; //取款标识符0xFF
        Util.arrayCopy(AppletID,(short)0,tmp, (short)2, (short)6); // 加入AID
        // 签名
        Signature(tmp);
        // 验签
        if (!VerifySignature(buffer))
        {
            JCSystem.abortTransaction();
            ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
        }
        if ((debitAmount > MAX_TRANSACTION_AMOUNT) || (debitAmount < 0)) {
            // 终止事务
            JCSystem.abortTransaction();
            ISOException.throwIt(SW_INVALID_TRANSACTION_AMOUNT);
        }

        if ((short) (balance - debitAmount) < (short) 0) {
            // 终止事务
            JCSystem.abortTransaction();
            ISOException.throwIt(SW_NEGATIVE_BALANCE);
        }

        balance = (short) (balance - debitAmount);
        // 添加记录
        record.AppendRecord(buffer, record.recordsize);
        // 提交事务
        JCSystem.commitTransaction();
    } // end of debit method

    private void getBalance(APDU apdu) {

        byte[] buffer = apdu.getBuffer();
        // 设置响应数据长度
        short le = apdu.setOutgoing();
        if (le < 2)
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

        apdu.setOutgoingLength((byte) 2);
        // 将响应数据写入buffer
        buffer[0] = (byte) (balance >> 8);
        buffer[1] = (byte) (balance & 0xFF);
        // 将在偏移地址0的位置的2byte的数据发送
        apdu.sendBytes((short) 0, (short) 2);

    } // end of getBalance method
    
    //PIN验证
    private void verify(APDU apdu) {

        byte[] buffer = apdu.getBuffer();

        byte byteRead = (byte) (apdu.setIncomingAndReceive());

        if (pin.check(buffer, ISO7816.OFFSET_CDATA, byteRead) == false)
            ISOException.throwIt(SW_VERIFICATION_FAILED);

    } // end of validate method

    private void grantPoints(short points) {

        // 共享接口实例
        JavaLoyaltyInterface loyaltySIO;
        // AID
        AID loyaltyAID;

        // 获取共享接口类的AID
        loyaltyAID = JCSystem.lookupAID(loyaltyAIDValue, (short) (0),
                (byte) (loyaltyAIDValue.length));
        // 获取共享接口实例引用
        if (loyaltyAID != null)
            loyaltySIO = (JavaLoyaltyInterface) JCSystem
                    .getAppletShareableInterfaceObject(loyaltyAID, (byte) 0);
        else
            return;

        // 传参
        loyaltySIO.grantPoints(points);
    }// end of grant points method

    // 读取记录文件
    private void ReadFile(APDU apdu) {

        // 验证身份
        if (!pin.isValidated())
            ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);

        byte[] buffer = apdu.getBuffer();
        byte[] data;
        short num = 0;
        // 判断参数
        if (buffer[ISO7816.OFFSET_P2] == 0x04) {
            num = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_P1]); // 将P1与0x00连接,num=00P1
        } else if (buffer[ISO7816.OFFSET_P2] == 0x00) {

        } else {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }
        // 判断是否超出了记录范围
        if (num > record.maxrecord) {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }
        // 判断记录文件是否有问题
        if (record.currentrecord == -1) {
            ISOException.throwIt(ISO7816.SW_DATA_INVALID);
        }
        num = (short) (record.currentrecord - num);
        if (num <= 0) {
            num = (short) (record.maxrecord + num);
        }
        data = record.ReadRecord(num);
        // 设置输出数据
        apdu.setOutgoing();
        apdu.setOutgoingLength(record.recordsize);
        apdu.sendBytesLong(data, (short) 0, record.recordsize);
    }

    // 内部认证函数
    private void indoAuthentication(APDU apdu) {

        byte[] buffer = apdu.getBuffer();
        apdu.setIncomingAndReceive();
        // 设置DES密钥
        indeskey.setKey(keyData, (short) 0);
        // 初始化密钥及加密模式
        inCipherObj.init(indeskey, Cipher.MODE_ENCRYPT);
        // 加密
        inCipherObj.doFinal(buffer, (short) 5, (short)8 , buffer, (short) 0);
        // 返回生成的8字节加密数据
        apdu.setOutgoingAndSend((short) 0, (short) 8);

    }

    // 外部认证随机数生成
    private void getRandom(APDU apdu) {

        byte[] buffer = apdu.getBuffer();
        apdu.setIncomingAndReceive();
        // 创建存放随机数的数组
        if (Random == null)
            Random = JCSystem.makeTransientByteArray((short) 16,
                    JCSystem.CLEAR_ON_DESELECT);

        // 获得生成随机数的对象实例
        RandomData ICC = RandomData.getInstance((byte) RandomData.ALG_PSEUDO_RANDOM);
        // 设置随机数的种子并产生8字节的随机数
        ICC.setSeed(Random, (short) 0, (short) 8);
        ICC.generateData(Random, (short) 0, (short) 8);
        // 返回生成的8字节随机数
        Util.arrayCopyNonAtomic(Random, (short) 0, buffer, (short) 0, (short) 8);
        apdu.setOutgoingAndSend((short) 0, (short) 8);

    }

    // 外部认证
    private void outdoAuthentication(APDU apdu) {

        byte[] buffer = apdu.getBuffer();
        apdu.setIncomingAndReceive();
        // 生成密钥对象
        outdeskey = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES,
                KeyBuilder.LENGTH_DES, false);
        // 设置DES密钥
        outdeskey.setKey(keyData, (short) 0);
        // 生成加密对象
        outCipherObj = Cipher.getInstance(Cipher.ALG_DES_CBC_ISO9797_M2, false);
        // 初始化密钥及加密模式
        outCipherObj.init(outdeskey, Cipher.MODE_ENCRYPT);
        // 加密
        outCipherObj.doFinal(Random, (short) 0, (short) 8, buffer, (short) 13);
        // 比较数据域与加密结果
        if (Util.arrayCompare(buffer, (short) 5, buffer, (short) 13, (short) 8) != 0)
            ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
    }
    
    //签名
    private void GenerateSignature(APDU apdu) {

        byte[] buffer = apdu.getBuffer();
        apdu.setIncomingAndReceive();
        //初始化签名模式
        sig1.init(mackey, Signature.MODE_SIGN);
                      
        //对输入数据进行签名,存放在buffer中        
        apdu.setOutgoingAndSend((short)13, sig1.sign(buffer, (short)5, (short)8,buffer, (short)13));

    }
    
    private short Signature(byte [] buffer) {
        //初始化签名模式
        sig.init(mackey, Signature.MODE_SIGN);                  
        //对输入数据进行签名,存放在buffer中
        return sig.sign(buffer, (short)0, (short)8, sigbuf, (short)0);            
    }
    
    //验签
    private boolean VerifySignature(byte [] buffer) {
                      
        //对存放在buffer中的输入数据进行签名验证
        if(Util.arrayCompare(buffer, (short)6, sigbuf, (short)0, (short)8)!=0)
            return false;
        else
            return true;
    }

}

然后下面是CyclicFile.java文件。该文件中的内容是交易记录功能实现的主要代码,包括存储交易记录,读取交易记录等内容。
CyclicFile.java:

package purse;

import javacard.framework.JCSystem;

public class CyclicFile {
    // 记录
    private byte[] record;
    //最大长度
    public short maxrecord;
    //记录长度
    public short recordsize;
    //当前记录位置
    public short currentrecord;
    //缓存区
    private byte[] buffer;
    
    protected CyclicFile(short size,short max)
    {
        recordsize = size;
        maxrecord = max;
        record = new byte[size*max];  // 相当于二维数组
        currentrecord = 0;
        buffer = JCSystem.makeTransientByteArray(size, JCSystem.CLEAR_ON_DESELECT);
    }
    // 读取某条记录
    public byte[] ReadRecord(short num)
    {
        for(short i=0;i<recordsize;i++)
        {
            buffer[i]=record[(num-1)*recordsize+i];
        }
        return buffer;
    }
    // 添加记录
    public short AppendRecord(byte[] data,short size)    
    {
        if(size>recordsize)  // 大于记录长度
        {
            return (short)1;
        }
        for(short i=0;i<size;i++){
            record[currentrecord*recordsize+i]=data[i];
        }
        for(short i=size;i<recordsize;i++){
            record[currentrecord*recordsize+i]=(byte)0x00;  //不足一条记录长度补零
        }
        currentrecord++;
        if(currentrecord==maxrecord)  //到达末尾返回头,循环
        {
            currentrecord=0;
        }
        return (short)0;
    }

}

2、sampleLoyalty package

sampleLoyalty包中是积分应用。该应用主要在电子钱包应用进行交易时修改积分应用中的积分。类似我们的一些联名卡,在一个应用消费时联名的应用也会添加积分这样的功能。
包中包括JavaLoyaltyInterface.java和JavaLoyalty.java两个文件。其中JavaLoyaltyInterface.java文件中定义了一个共享类,作用主要是为了实现跨防火墙的功能。
JavaLoyaltyInterface.java:

package sampleLoyalty;

import javacard.framework.Shareable;

public interface JavaLoyaltyInterface extends Shareable {
    void grantPoints (short points);
}

JavaLoyalty.java:

package sampleLoyalty;

import javacard.framework.AID;
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.Shareable;
import javacard.framework.Util;

public class JavaLoyalty extends Applet implements JavaLoyaltyInterface {

    final static byte LOYALTY_CLA = (byte) 0x90; // CLA
    final static byte READ_BALANCE = (byte) 0x20; // 读取积分
    final static byte RESET_BALANCE = (byte) 0x22; // 清空积分
    final static byte CREDIT = (byte) 0x01; // 存款
    final static byte DEBIT = (byte) 0x02; // 取款
    final static short TRANSACTION_AMOUNT_OFFSET = 0;
    final static short SCALE = (short) 1; // 比例,$1=1积分
    final static short BALANCE_MAX = (short) 30000; 
    
    short balance;

    /**
     * Installs Java Loyalty applet.
     * 
     * @param bArray
     *            install parameter array.
     * @param bOffset
     *            where install data begins.
     * @param bLength
     *            install parameter data length.
     */
    public static void install(byte[] bArray, short bOffset, byte bLength) {
        new JavaLoyalty(bArray, bOffset, bLength);
    }

    /**
     * Performs memory allocations, initializations, and applet registration
     * 
     * @param bArray
     *            received by install.
     * @param bOffset
     *            received by install.
     * @param bLength
     *            received by install.
     */
    protected JavaLoyalty(byte[] bArray, short bOffset, byte bLength) {
        balance = (short) 0;
        /*
         * if AID length is not zero register Java Loyalty applet with specified
         * AID
         * 
         * NOTE: all the memory allocations should be performed before
         * register()
         */
        byte aidLen = bArray[bOffset];
        if (aidLen == (byte) 0) {
            register();
        } else {
            register(bArray, (short) (bOffset + 1), aidLen);
        }
    }

    /**
     * Implements getShareableInterfaceObject method of Applet class.
     * <p>
     * JavaLoyalty could check here if the clientAID is that of JavaPurse
     * Checking of the parameter to be agreed upon value provides additional
     * security, or, if the Shareable Interface Object weren't JavaLoyalty
     * itself it could return different Shareable Interface Objects for
     * different values of clientAID and/or parameter.
     * <p>
     * See<em>Java Card Runtime Environment (JCRE) Specification</em> for
     * details.
     * 
     * @param clientAID
     *            AID of the client
     * @param parameter
     *            additional parameter
     * @return JavaLoyalty object
     */
    public Shareable getShareableInterfaceObject(AID clientAID, byte parameter) {
        // 返回当前对象的引用
        if (parameter == (byte) 0)
            return this;
        else
            return null;
    }

    /**
     * Implements main interaction with a client. The data is transfered through
     * APDU buffer which is a global array accessible from any context. The
     * format of data in the buffer is subset of Transaction Log record format:
     * 2 bytes of 0, 1 byte of transaction type, 2 bytes amount of transaction,
     * 4 bytes of CAD ID, 3 bytes of date, and 2 bytes of time. This sample
     * implementation ignores everything but transaction type and amount.
     * 
     * @param buffer
     *            APDU buffer
     */
    public void grantPoints(short points) {
        balance = (short) (balance + points);
        if (balance > BALANCE_MAX)
            balance = BALANCE_MAX;
    }

    /**
     * Dispatches APDU commands.
     * 
     * @param apdu
     *            APDU
     */
    public void process(APDU apdu) {
        byte[] buffer = apdu.getBuffer();
        
        buffer[ISO7816.OFFSET_CLA] = (byte) (buffer[ISO7816.OFFSET_CLA] & (byte) 0xFC);

        if (buffer[ISO7816.OFFSET_CLA] == LOYALTY_CLA) {
            switch (buffer[ISO7816.OFFSET_INS]) {
            case READ_BALANCE:
                processReadBalance(apdu);
                break;
            case RESET_BALANCE:
                processResetBalance();
                break;
            default:
                ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
            }
        } else if (selectingApplet())
            return;
        else
            ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
    }

    void processReadBalance(APDU apdu) {
        byte[] buffer = apdu.getBuffer();
        Util.setShort(buffer, (short) 0, balance);
        apdu.setOutgoingAndSend((short) 0, (short) 2);
    }

    void processResetBalance() {
        balance = (short) 0;
    }

}

0x04 测试结果

首先启动后应用输入:/select 112233445501,即我们的多功能电子钱 包程序AID。这样就能选择我们的程序。然后输入PIN码,返回码90 00, 证明成功验证,如下图:
执行结果
接下来可以存款,输入命令/send 803000000164,这样就可以存款$100,通过命令/send 805000000002 可以获取当前卡中余额,可见,返回 00 64,证明卡中已有余额$100,证明上面存款结果成功,如下图所示:
执行结果
然后测试一下取款,通过命令/send 804000000132,我们可以取款$50, 然后再用命令/send 805000000002 获取当前卡中余额,可见返回值为 00 32,证明取款了$50,还有余额$50,如下图所示:

执行结果
然后我们可以查找当前的记录值,查找第一条到第三条记录,第一条记录也就是最近一次的记录是我们取款了$50,第二条记录就 是我们存款了$100。第三条记录为空,证明现在一共有两条记录,正好对 应了我们上面的两次存取款操作,如下:
执行结果
然后我们还可以在积分应用中查看当前积分情况, 首先使用/select 223344556601 选择积分程序,然后输入命令/send 9020000000,查看当前 积分情况,结果如下:
执行结果
然后进行内部验证,终端发送命令/send 80880000081122334455667788, 也就是终端发送8Bytes随机数“11 22 33 44 55 66 77 88”给Applet。然后Applet
响应数据为6c5e94dcadd39f1d9000,也就是加密后的随机数为“6C 5E 94 DC AD D3 9F 1D”,经过验证,正确加密!如下:
执行结果
然后进行外部认证,首先使用命令/send 8084000000获取随机数,程序响 应数据为426C49648EFC40FC9000,也就是8Bytes随机数为“42 6C 49 64 8E FC 40 FC”。如下图:
执行结果
然后加密随机数后使用命令/send 8082000008eb714536c71e7c68发送加密 后的随机数,也就是“EB 71 45 36 C7 1E 7C 68”,这就是刚才的随机数加密 后的结果,发送给Applet后返回状态码为90 00,证明验证正确:
执行结果
然后我们再次使用命令/send 805000000002查询卡中的余额,发现返回数 据为00 64,证明当前卡中余额为$100,存款成功,证明MAC验证通过!如下:

执行结果
下一步我们决定取款$50,所以我们使用命令/send 804000000932f4922fbaa52053ab,也就是MAC校验码为“32 F4 92 2F BA A5 20 53 AB”,发送命令后返回90 00,无错误发生。然后我们再次使用命令/send 805000000002查询卡中的余额,发现返回数 据为00 32,之前卡中余额为0x64,现在卡中余额为0x32,也就是$50,证明当 前卡中余额为$50,取款成功,证明MAC验证通过!如下:
执行结果

0x05 后记

实验内容差不多就是上面这些内容,大部分内容都涉及到了,唯一缺少的就是对APDU命令的设计我在这里没有写,如果有需要可以在下面评论。如果有需要实验指导书的也可以在下面评论,回头我可以把实验指导书放上GitHub供大家参考。总的来说本次实验还是让我感到比较新奇的,从前也没有涉及到过智能卡应用的编写,也算是经过本次实验涉足到了新的领域,刚开始肯定有许多不足,以后再改进吧hhh

源码已上传GitHub:源代码

END
本文作者:
文章标题:智能卡安全课程实验
本文地址:http://hackerhome.top/index.php/archives/8/
版权说明:若无注明,本文皆由"岁月年华的秘密基地"原创,转载请保留文章出处。
最后修改:2021 年 02 月 13 日 12 : 46 AM
如果觉得我的文章对你有用,请随意赞赏