上一篇介绍了如何创建文件,本篇介绍如何将密钥或者持卡人信息等写入相应的文件去,以及如何从文件中读取应用或者持卡人的信息。
首先是写入密钥write_key,密钥分为三种:TAC密钥、圈存密钥、消费密钥:
其实这三种密钥在写入文件里去的时候并不会产生什么区别,区别是在使用密钥的时候体现。之前已经建好了keyfile文件(所以判断如果没建就抛出异常),所以直接调用keyfile数据结构里面的addkey函数进行密钥增加:
public void addkey(byte tag, short length, byte[] value){ byte[] pbuf;//只做暂存,密钥要存进Key[recNum]中 pbuf = new byte[23]; Key[recNum] = pbuf;//可看成给二维数组复制 pbuf[0] = tag; pbuf[1] = (byte)length; Util.arrayCopyNonAtomic(value, (short)0, pbuf, (short)2, length); recNum ++; }
所以tag参数对应的是apdu命令的p2参数,密钥长度对应的而是apdu的lc,值对应data。
然后就是write_binary,写二进制文件(应用基本信息和持卡人基本信息),类似的,调用BinaryFile数据结构里面的write_bineary函数(这个其实是binary,TA给的代码命名错了):
/* * 功能:写入二进制 * 参数:off 写入二进制文件 的偏移量; dl 写入的数据长度; data 写入的数据 * 返回:无 */ public final void write_bineary(short off, short dl, byte[] data){ Util.arrayCopyNonAtomic(data, (short)0, binary, off, dl); }
最后是读取二进制文件信息read_binary,理解了上面这里就很简单了,没什么好说的,直接调用BinaryFile结构体里面的read_binary函数:
/* * 功能:读二进制文件 * 参数:off 二进制读取的偏移量;len 读取的长度; data 二进制数据的缓冲区 * 返回:二进制数据的字节长度 */ public final short read_binary(short off, short len, byte[] data){ Util.arrayCopyNonAtomic(binary, off, data, (short)0, len); return len; }
purse.java
//主文件:电子钱包,调用了其他几个辅助类。 //注意这些所有的代码和应用都是运行在卡片中的! //而终端只是在命令行敲几句命令发送的那里. package purse; //import Purse; import javacard.framework.APDU; import javacard.framework.Applet; import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.framework.Util; public class Purse extends Applet { //APDU Object private Papdu papdu; //文件系统,每次run的时候都相当于一张空白卡片,全部需要重新建立, //并且建立之后就归当前run的applet所有,某个文件可以随时被调用(keyfile) private KeyFile keyfile; //密钥文件 private BinaryFile cardfile; //应用基本文件 private BinaryFile personfile; //持卡人基本文件 private EPFile EPfile; //电子钱包文件 Electronic Purse public Purse(byte[] bArray, short bOffset, byte bLength){ papdu = new Papdu(); byte aidLen = bArray[bOffset]; if(aidLen == (byte)0x00) register(); else register(bArray, (short)(bOffset + 1), aidLen);//注册applet } //安装applet public static void install(byte[] bArray, short bOffset, byte bLength) { new Purse(bArray, bOffset, bLength); } //执行applet public void process(APDU apdu) { if (selectingApplet()) { return; } //步骤1:取APDU缓冲区数组引用并将之赋给新建数组 byte buffer[] = apdu.getBuffer(); //步骤2:取APDU缓冲区中数据放到变量papdu short lc = apdu.setIncomingAndReceive();//将apdu读取到卡片缓冲区当中并返回data段的长度 papdu.cla = buffer[ISO7816.OFFSET_CLA]; papdu.ins = buffer[ISO7816.OFFSET_INS]; papdu.p1 = buffer[ISO7816.OFFSET_P1]; papdu.p2 = buffer[ISO7816.OFFSET_P2]; Util.arrayCopyNonAtomic(buffer, ISO7816.OFFSET_CDATA, papdu.pdata, (short)0, lc); //步骤3:判断命令APDU是否包含数据段,有数据则获取数据长度,并对le赋值, //否则,即不需要lc和data,则获取缓冲区原本lc实际上是le //获取le的方法,因为不确定papdu有le部分,所以IOS7816下标可选项并没有le而是放在数据块中的. //如果有数据块,那le就是buffer[ISO7816.OFFSET_CDATA+lc] //调用papdu函数判断,不能直接通过lc判断,因为没lc只有le也会把le赋给lc if(papdu.APDUContainData())//若papdu命令包含数据块 { papdu.le = buffer[ISO7816.OFFSET_CDATA+lc]; papdu.lc = buffer[ISO7816.OFFSET_LC]; } else { papdu.le = buffer[ISO7816.OFFSET_LC];//若没data部分则lc部分实际是le papdu.lc = 0; } boolean rc = handleEvent(); //步骤4:判断是否需要返回数据,并设置apdu缓冲区 if(papdu.le != 0) { Util.arrayCopyNonAtomic(papdu.pdata, (short)0, buffer, ISO7816.OFFSET_CDATA, (short)papdu.pdata.length); apdu.setOutgoingAndSend((short)5, (short)papdu.pdata.length);//把缓冲区的数据返回给终端 } } /* * 功能:对命令的分析和处理 * 参数:无 * 返回:是否成功处理了命令 */ private boolean handleEvent(){ switch(papdu.ins){ case condef.INS_CREATE_FILE: return create_file(); //todo:完成写二进制命令,读二进制命令,写密钥命令 case condef.INS_WRITE_KEY: return write_key(); case condef.INS_WRITE_BIN: return write_bin(); case condef.INS_READ_BIN: return read_bin(); case condef.INS_NIIT_TRANS: if(papdu.p1 == (byte)0x00) return init_load(); if(papdu.p1 == (byte)0x01) return init_purchase(); ISOException.throwIt(ISO7816.SW_WRONG_P1P2);//else抛出异常 case condef.INS_LOAD: return load(); } ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); return false; } /* * 功能:创建文件 */ private boolean create_file() { switch(papdu.pdata[0]){ //data的第一位表示要创建文件的类型 case condef.EP_FILE: return EP_file(); //todo:完成创建密钥文件,持卡人基本文件和应用基本文件 case condef.KEY_FILE: return Key_file(); case condef.CARD_FILE: return Card_file(); case condef.PERSON_FILE: return Person_file(); default: ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED); } return true; } /* * 功能:创建电子钱包文件 */ private boolean EP_file() { if(papdu.cla != (byte)0x80) ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); if(papdu.lc != (byte)0x07) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); if(EPfile != null)//有文件了还重复创建则会报错 ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); if(keyfile == null)//都还没密钥文件(必须先于任何其他文件创建) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); this.EPfile = new EPFile(keyfile); return true; } //创建密钥文件 private boolean Key_file() { if(papdu.cla != (byte)0x80) ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); if(papdu.lc != (byte)0x07) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); if(keyfile != null)//有文件了还重复创建则会报错 ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); this.keyfile = new KeyFile(); return true; } //创建应用基本文件 private boolean Card_file() { if(papdu.cla != (byte)0x80) ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); if(papdu.lc != (byte)0x07) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); if(cardfile != null)//有文件了还重复创建则会报错 ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); if(keyfile == null)//都还没密钥文件(必须先于任何其他文件创建) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); this.cardfile = new BinaryFile(papdu.pdata);//传进的参数就是要写入的内容 return true; } //创建持卡人信息文件 private boolean Person_file() { if(papdu.cla != (byte)0x80) ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); if(papdu.lc != (byte)0x07) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); if(personfile != null)//有文件了还重复创建则会报错 ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); if(keyfile == null)//都还没密钥文件(必须先于任何其他文件创建) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); this.personfile = new BinaryFile(papdu.pdata);//传进的参数就是要写入的内容 return true; } //写入一条密钥 private boolean write_key() { if(keyfile == null)//都还没密钥文件 ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); if(papdu.cla != (byte)0x80) ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); //文件标识有错,这句判断写得有问题啊,老是会返回这个异常--已解决,应该用and而不是or,因为要三个情况都不是才异常 if(papdu.p2 != (byte)0x06 && papdu.p2 != (byte)0x07 && papdu.p2 != (byte)0x08) ISOException.throwIt(ISO7816.SW_WRONG_P1P2); if(papdu.lc == 0 || papdu.lc > 21)//密钥长度不能为0也不能超过21 ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); if(keyfile.recNum >= 3)//文件空间已满 ISOException.throwIt(ISO7816.SW_FILE_FULL); this.keyfile.addkey(papdu.p2, papdu.lc, papdu.pdata);//写入一条密钥 return true; } //写入二进制文件 private boolean write_bin() { if(keyfile == null)//都还没密钥文件 ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); //都还没二进制文件--没找到 if(cardfile == null && papdu.p1 == (byte)0x16) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); if(personfile == null && papdu.p1 == (byte)0x17) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); if(papdu.cla != (byte)0x00) ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); /*if(papdu.p2 == 0)//没有文件标识 ISOException.throwIt(ISO7816.SW_WRONG_P1P2);*/ if(papdu.lc == 0)//写入长度不能为0 ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); //写入一条二进制命令到文件 if(papdu.p1 == (byte)0x16)//表明写入的是应用信息 this.cardfile.write_bineary(papdu.p2, papdu.lc, papdu.pdata); else if(papdu.p1 == (byte)0x17)//表明写入的是持卡人信息 this.personfile.write_bineary(papdu.p2, papdu.lc, papdu.pdata); return true; } //读取二进制文件 private boolean read_bin() { if(keyfile == null)//都还没密钥文件 ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); //都还没二进制文件--没找到 if(cardfile == null && papdu.p1 == (byte)0x16) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); if(personfile == null && papdu.p1 == (byte)0x17) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); if(papdu.cla != (byte)0x00) ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); /*if(papdu.p2 == 0)//没有说明读取文件偏移量 ISOException.throwIt(ISO7816.SW_WRONG_P1P2);*/ //读取相应的二进文件 if(papdu.p1 == (byte)0x16)//表明读取的是应用文件 this.cardfile.read_binary(papdu.p2, papdu.le, papdu.pdata); else if(papdu.p1 == (byte)0x17)//表明读取的是持卡人信息文件 this.personfile.read_binary(papdu.p2, papdu.le, papdu.pdata); return true; } /* * 功能:圈存命令的实现 */ private boolean load() { short rc; if(papdu.cla != (byte)0x80) ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); if(papdu.p1 != (byte)0x00 && papdu.p2 != (byte)0x00) ISOException.throwIt(ISO7816.SW_WRONG_P1P2); if(EPfile == null) ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); if(papdu.lc != (short)0x0B) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); /*if(keyfile.findkey(papdu.p2) == 0)//通过标识找不到相应的密钥文件 ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);*/ rc = EPfile.load(papdu.pdata); if(rc == 1) ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); else if(rc == 2) ISOException.throwIt(condef.SW_LOAD_FULL); else if(rc == 3) ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND); papdu.le = (short)4; return true; } /* * 功能:圈存初始化命令的实现 */ private boolean init_load() { short num,rc; if(papdu.cla != (byte)0x80) ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); if(papdu.p1 != (byte)0x00 && papdu.p2 != (byte)0x02) ISOException.throwIt(ISO7816.SW_WRONG_P1P2); if(papdu.lc != (short)0x0B) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); if(EPfile == null) ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); num = keyfile.findkey(papdu.pdata[0]); if(num == 0x00) ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND); rc = EPfile.init4load(num, papdu.pdata); if(rc == 2) ISOException.throwIt((condef.SW_LOAD_FULL)); papdu.le = (short)0x10; return true; } /* * 功能:消费命令的实现 */ private boolean purchase(){ return true; } /* * 功能:余额查询功能的实现 */ private boolean get_balance(){ return true; } /* * 功能:消费初始化的实现 */ private boolean init_purchase(){ return true; } }
//这个文件表示的是密钥文件 package purse; import javacard.framework.Util; public class KeyFile { public short size; //记录的最大存储数量 public short recNum; //当前所存储的记录数量 private Object[] Key; //密钥记录 public KeyFile(){ size = 4; recNum = 0; Key = new Object[size];//每一位表示一个密钥,可看成是一个二维数组 } /* * 功能:添加密钥 * 参数:tag 密钥标识符; length 数值的长度;value 数值(5个字节的密钥头+16个字节的密钥值) * 返回:无 */ public void addkey(byte tag, short length, byte[] value){ byte[] pbuf;//只做暂存,密钥要存进Key[recNum]中 pbuf = new byte[23]; Key[recNum] = pbuf;//可看成给二维数组复制 pbuf[0] = tag; pbuf[1] = (byte)length; Util.arrayCopyNonAtomic(value, (short)0, pbuf, (short)2, length); recNum ++; } /* * 功能:通过密钥标识符获取密钥记录号 * 参数:tag 密钥标识符 * 返回:记录号 */ public short findkey(byte Tag){ byte[] pbuf; if(recNum == 0) return 0; for(short i = 0;i < recNum;i ++){ pbuf = (byte[])Key[i]; if(pbuf[0] == Tag) return (short)(i + 1); } return 0; } /* * 功能:通过密钥类型获取密钥记录号 * 参数:type 密钥类型 * 返回:记录号 */ public short findKeyByType(byte Type){ byte[] pbuf; if(recNum == 0) return 0; for(short i = 0;i < recNum;i ++){ pbuf = (byte[])Key[i]; if(pbuf[2] == Type) return (short)(i + 1); } return 0; } /* * 功能:通过密钥记录号读取密钥 * 参数:num 密钥记录号 data 所读取到的密钥缓冲区 * 返回:密钥的长度 */ public short readkey(short num, byte[] data){ byte[] pdata; pdata = (byte[])Key[num - 1]; Util.arrayCopyNonAtomic(pdata, (short)2, data, (short)0, (short)(pdata[1])); return (short)(pdata[1] - 5); } }
//这个文件表示的是二进制文件 package purse; import javacard.framework.Util; public class BinaryFile { private short size; //二进投文件的大小 private byte[] binary; //二进制文件 public BinaryFile(byte[] pdata){ size = Util.makeShort(pdata[1], pdata[2]); binary = new byte[size]; } /* * 功能:写入二进制 * 参数:off 写入二进制文件 的偏移量; dl 写入的数据长度; data 写入的数据 * 返回:无 */ public final void write_bineary(short off, short dl, byte[] data){ Util.arrayCopyNonAtomic(data, (short)0, binary, off, dl); } /* * 功能:读二进制文件 * 参数:off 二进制读取的偏移量;len 读取的长度; data 二进制数据的缓冲区 * 返回:二进制数据的字节长度 */ public final short read_binary(short off, short len, byte[] data){ Util.arrayCopyNonAtomic(binary, off, data, (short)0, len); return len; } /* * 功能:获取二进制文件大小 * 参数:无 * 返回:二进制文件的大小 */ public final short get_size(){ return size; } }
这里只贴No Error也就是执行正确命令的运行图,自己也试了一些错误命令的调试,也就是改改命令脚本的命令,看看会不会出现一些异常,同时查看自己是否没考虑完全异常情况的判断,这里就不贴图了,这个需要对读写文件的apdu命令和几大重要参数有很好的理解,所以不懂就回去看看实验2文档和ppt,还有代码。