写入文件和读取文件信息—Java Card开发第三篇

上一篇介绍了如何创建文件,本篇介绍如何将密钥或者持卡人信息等写入相应的文件去,以及如何从文件中读取应用或者持卡人的信息。

首先是写入密钥write_key,密钥分为三种:TAC密钥、圈存密钥、消费密钥:

写入文件和读取文件信息—Java Card开发第三篇_第1张图片


其实这三种密钥在写入文件里去的时候并不会产生什么区别,区别是在使用密钥的时候体现。之前已经建好了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 ++;
	}

可以看到,使用addkey函数需要传递三个参数进去,第一个是标签,也就是密钥的类型(刚才说的三种:圈存、消费、TAC),第二个是密钥的长度,第三个是密钥的值。对应的是apdu里面的哪些参数呢?看下图:


写入文件和读取文件信息—Java Card开发第三篇_第2张图片


所以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;
	}
}

KeyFile.java

//这个文件表示的是密钥文件
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);
	}
	
}

BinaryFile.java

//这个文件表示的是二进制文件
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;
	}
}

最后是运行命令脚本和运行结果:

写入文件和读取文件信息—Java Card开发第三篇_第3张图片



写入文件和读取文件信息—Java Card开发第三篇_第4张图片

写入文件和读取文件信息—Java Card开发第三篇_第5张图片


这里只贴No Error也就是执行正确命令的运行图,自己也试了一些错误命令的调试,也就是改改命令脚本的命令,看看会不会出现一些异常,同时查看自己是否没考虑完全异常情况的判断,这里就不贴图了,这个需要对读写文件的apdu命令和几大重要参数有很好的理解,所以不懂就回去看看实验2文档和ppt,还有代码。

你可能感兴趣的:(java,card,java智能卡开发)