EMV读卡器终端数据库的设计

本文探讨了EMV非接触金融卡读卡器的数据库设计,特别是如何在单片机上高效存储和管理多个组织的配置数据。采用增量式数据库方法,将通用数据存入Group0,协议特定数据存入GroupN0,通过TLV格式管理数据,实现空间利用和灵活性的平衡。在更新和删除时,通过比较和碎片整理来优化存储空间。
摘要由CSDN通过智能技术生成
EMV非接触金融卡读卡器内一般同时有多个组织的读卡卡协议,如 MASTERCARD PayPass、VISA、AmexExpressPay、DISCOVER、INTERAC、JCB等。各个组织的卡一般都有不同的终端数据配置,即不同的CONFIGURE文件。如何存储管理调用这些配置数据是EMV读卡器开发过程中的基础问题,本文给出一种在单片微型处理器上同时实现协议并数据管理的简洁高效的方式,即Level2和Database均在一个芯片上完成。 

说白了就是把代码和数据都存到单片机的FLASH上,划分好各自的区域就是了。单片机里的FLASH一般是NOR FLASH,用作数据库存储媒体需要考虑其寿命和最小写入单元及擦除块的大小问题。EMV采用TLV格式管理数据(TagLengthValue).不同协议的卡的终端数据有大部分是通用的,即都用相同的Tag相同的Value,只有少部分是各自增加的Tag或相同的Tag用不同的Value。是此,我们采用增量式数据库方式,最大限度减少Flash存储空间同时又最大限度保持各协议所属库的灵活性。将Flash数据空间分成A、B两大块:A块空间做通用库,我们称为Group0,另一块空间B存储各个协议属库,称为GroupN0。Group0里可以对各个数据项单独管理(存储、更新、擦除),以Tag为单位;GroupN0是以库为单位写入和擦除及更新。同时B区还可以存储各AID属性参数和各RID的CAPK,对B存储空间数据块的索引就分为三种类型,可作不同的标志区分。Aid和CAPK的管理相比较简单些,此处只对Group0和GroupN0的存储说几句。

Group0存储以单个数据项为单位,也可说是以Tag为单位,需要处理对每个Tag存储、索引、更新、删除。Flash不能对每个存储字节即擦即写,就像EEPROM那样,它需要以一个SECTOR为单位擦除,且最小写入单位各种厂家的芯片不一样,2、4、8字节都有,还有的允许单个位(bit)由1写到0而最小写入单元里的其它位写入1位即可,所以Group0里是否允许连续存储和每个Tag之间会浪费几个字节是依芯片而定的,0到7个Bytes不一。我用的芯片经测试可以稳妥地接受单个Bit的写入,所以可以实现完全连续的写入,即连续的两个数据之间可以一个BYTE都不浪费。但每个数据项之前仍然要加入一个字节表示这数据TLV是否有效,当然是FF有效,无效时写成00即可,用于删除更新。TLV格式有一定规范,自身包含长度信息。所以索引方式就清晰了:从A区头开始查找有效TLV,计算长度调到下一个TLV,直到找到你需要的Tag。TLV不重复,就要求存储更新时先搜索,比对,删除后再在尾部加入。无效的数据也占用空间,这个没办法,满了咋办?简单,把有效的TLV读出来,擦除后再写回去,有点儿类似硬盘碎片整理。数据量可能很大,可以以Sector为单位读取有效TLV,然后擦除该Sector,再把有效数据写回去,记得是挨着写,空间就腾出来了。
package br.com.paysmart; import java.util.List; import javax.smartcardio.*; import java.util.*; import java.text.SimpleDateFormat; public class paySmartTest { private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray(); public static String toHexString(byte[] buf) { char[] chars = new char[2 * buf.length]; for (int i = 0; i < buf.length; ++i) { chars[2 * i] = HEX_CHARS[(buf[i] & 0xF0) >>> 4]; chars[2 * i + 1] = HEX_CHARS[buf[i] & 0x0F]; } return new String(chars); } public static byte[] hexStringToByteArray(String s) { s = s.replaceAll("\\W+",""); int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16)); } return data; } public static byte[] concat( byte[]...arrays ) { // Determine the length of the result array int totalLength = 0; for (int i = 0; i < arrays.length; i++) { totalLength += arrays[i].length; } // create the result array byte[] result = new byte[totalLength]; // copy the source arrays into the result array int currentIndex = 0; for (int i = 0; i < arrays.length; i++) { System.arraycopy(arrays[i], 0, result, currentIndex, arrays[i].length); currentIndex += arrays[i].length; } return result; } public static byte[] getCurrentDateAsByteArray( String sFormat ) { SimpleDateFormat sdfDateFormatted = new SimpleDateFormat( sFormat ); Date now = new Date(); String sDate = sdfDateFormatted.format(now); return hexStringToByteArray ( sDate ); } public static byte[] extractTLVFromBuffer( int iTag, byte[] buf ) { //@todo: check size, check 77, etc int i; byte[] bufExtracted = buf; TLV tlvBuf = new TLV( buf, 0 ); if ( iTag <= 0xFF ) { //System.out.println("iTag= "+ iTag + " tlvBuf.type= " + tlvBuf.type + " buf= "+ toHexString( buf ) ); if ( tlvBuf.type == iTag ) { //System.out.println("Found tag = "+ iTag + " " + toHexString( tlvBuf.getValue() ) ); return tlvBuf.getValue(); } else { /* TLV tlvNext = tlvBuf.next; if ( tlvNext != null ) System.out.println( "next = " + toHexString( tlvNext.getDERData() ) ); else System.out.println( "next = NULL" ); while ( tlvNext != null ) { System.out.println("traversing next, looking for "+ iTag ); return extractTLVFromBuffer( iTag, tlvBuf.next.getDERData() ); } */ return extractTLVFromBuffer( iTag, tlvBuf.getValue() ); } } // lookup for iTag else { //@todo double tags /* byte bTag1 = (byte) ( iTag & 0x00FF ); byte bTag2 = (byte) (( iTag & 0xFF00 ) >> 2 ); */ } return buf; } public static String TLVListToString( byte[] bufTLV ) { String sList = ""; int iOffset = 0; int iTLV= 0; System.out.println("[TLVListToString]"); while ( iOffset < bufTLV.length ) { TLV tlvCurrent = new TLV( bufTLV, iOffset ); iTLV++; System.out.println(" TLV[" + iTLV + "].tag = " + tlvCurrent.type ); System.out.println(" TLV[" + iTLV + "].size = " + tlvCurrent.length ); System.out.println(" TLV[" + iTLV + "].value = " + toHexString(tlvCurrent.getValue() ) ); iOffset+= tlvCurrent.getDERData().length; //System.out.println(" TLV["+iTLV+"].DER = " + toHexString(tlvCurrent.getDERData() ) ); System.out.println( iOffset ); System.out.println(); sList += toHexString( tlvCurrent.getValue() ) + "\n"; } return sList; } public static String printAPDU( ResponseAPDU apdu ) { String sAux = apdu.toString() + " data=" + toHexString( apdu.getData() ); return sAux; } public static void main( String[] args ) { try { boolean boolEverythingAsTLV = true; int i; Random RandomNumberGenerator = new Random(); System.out.println("_______________________________________________________"); System.out.println("paySmart EMV Minimalist Terminal v1.0"); System.out.println("www.paySmart.com.br"); System.out.println("_______________________________________________________"); System.out.println(""); for (i=0; i<args.length; i++) System.out.println( "Param " + i + ": "+args[i] ); System.out.println(); if ( args.length < 2 ) { System.out.println(""); System.out.println("usage: paySmartTest #reader AID [Host Port]"); System.out.println(" example1> paySmartTest 0 A0000005551010"); System.out.println(" example2> paySmartTest 1 A000000555E010"); System.out.println(" example3> paySmartTest 1 A0000000041010"); System.out.println(" example4> paySmartTest 1 A0000000041010 10.2.1.1 887"); System.out.println(""); System.out.println("#ERROR# Invalid Params"); System.exit(1); // throw new Exception( "Invalid params" ); } TerminalFactory factory = TerminalFactory.getDefault(); List<CardTerminal> terminals = null; // Display the list of terminals try { terminals = factory.terminals().list(); System.out.println("Smart card readers: "); System.out.println( terminals ); } catch(Exception e) { System.out.println("#ERROR# No readers found"); System.exit(2); //throw new Exception( "No readers found" ); } int iReader=0; // Use the first terminal try { iReader = Integer.parseInt( args[0] ); } catch( Exception e ) { System.out.println("#ERROR# Invalid reader index format '"+args[0]+"'" ); System.exit(3);// throw new Exception( "Invalid reader index '"+args[0]+"'" ); } if ( iReader >= terminals.size() ) { System.out.println("#ERROR# Invalid reader index '"+args[0]+"'. Last valid index is " + (terminals.size()-1) ); System.exit(3); // throw new Exception( "Invalid reader index '"+iReader+"'. Last valid index is "+(terminals.size()-1) ); } CardTerminal terminal = terminals.get( iReader ); // Connect with the card Card card = null; CardChannel channel = null; try { card = terminal.connect("*"); System.out.println(" card: " + card ); channel = card.getBasicChannel(); System.out.println(" ATR=" + toHexString( card.getATR().getBytes() ) ); System.out.println( "" ); } catch( Exception e ) { System.out.println("#ERROR# Cannot connect to card"); System.exit(4); // throw new Exception( "Cannot connect to card" ); } // Send Select Application command //String sAID = "A000000555E010"; // MAIS ACEITO //String sAID = "A0 00 00 05 55 10 10"; String sAID = args[1]; byte[] bufAID = hexStringToByteArray( sAID ); System.out.println( "SELECT (AID = " + toHexString( bufAID ) + ")" ); ResponseAPDU answer = channel.transmit(new CommandAPDU(0x00, 0xA4, 0x04, 0x00, bufAID )); System.out.println( " FCI = "+ toHexString( answer.getData() ) ); System.out.println( "" ); if ( answer.getSW() != 0x9000 ) { System.out.println("#ERROR# Invalid AID '"+sAID+"'" ); System.exit(5); //throw new Exception( "Invalid AID '"+sAID+"'" ); } //Open MEL Application byte[] bufMEL = {(byte) 0x83, 0x00 }; System.out.println( "MEL (MEL = " + toHexString( bufMEL ) + ")" ); answer = channel.transmit( new CommandAPDU(0xBE, 0x12, 0x00, 0x00, bufMEL, 0x00 ) ); System.out.println( " " + printAPDU( answer ) ); //Send GPO byte[] bufPDOL = {(byte) 0x83, 0x00 }; System.out.println( "GPO (PDOL = " + toHexString( bufPDOL ) + ")" ); answer = channel.transmit( new CommandAPDU(0x80, 0xA8, 0x00, 0x00, bufPDOL ) ); System.out.println( " " + printAPDU( answer ) ); byte[] bufAIP = extractTLVFromBuffer( 0x82, answer.getData() ); //byte[] bufAFL = extractTLVFromBuffer( 0x94, answer.getData() ); System.out.println( " AIP = " + toHexString( bufAIP ) ); //System.out.println( " AFL = " + toHexString( bufAIP ) ); System.out.println( "" ); //Send ReadRecord 2 System.out.println( "READ RECORD (SFI=1, REC=2)" ); answer = channel.transmit( new CommandAPDU(0x00, 0xB2, 0x02, 0x0C, 0x3D ) ); //fixed :-( @todo GetResponse System.out.println( printAPDU( answer ) ); byte[] bufExpiryDate = {0x17, 0x01, 0x01}; //Arrays.copyOfRange( answer.getData(), 11, 03 ); System.out.println( " ExpiryDate= " + toHexString( bufExpiryDate ) ); byte [] bufSlice = Arrays.copyOfRange( answer.getData(), 14, answer.getData().length-14 ); byte[] bufPAN = extractTLVFromBuffer( 0x5A, bufSlice ); System.out.println( " PAN = " + toHexString(bufPAN) ); byte[] bufPANSequence = { 0x00 }; // @todo extractTLVFromBuffer( 0x5F34, ... ); System.out.println( " PANSequence = " + toHexString(bufPANSequence) ); System.out.println( "" ); //Send ReadRecord 3 System.out.println( "READ RECORD (SFI=1, REC=3)" ); answer = channel.transmit( new CommandAPDU(0x00, 0xB2, 0x03, 0x0C, 0x43 ) ); //fixed :-( @todo GetResponse System.out.println( printAPDU( answer ) ); byte[] bufSlice2 = Arrays.copyOfRange( answer.getData(), 2, answer.getData().length-2 ); byte[] bufTrack2 = extractTLVFromBuffer( 0x57, bufSlice2 ); System.out.println( " Track2= " + toHexString( bufTrack2 ) ); System.out.println( ); byte[] bufAmountAuthorized = hexStringToByteArray ("00 00 00 00 00 00"); // 9F02 06 ;$0.00 (assuming currency exponent = 2) byte[] bufAmountOther = hexStringToByteArray ("00 00 00 00 00 00"); // 9F03 06 ;$0.00 (assuming currency exponent = 2) byte[] bufTermCountryCode = hexStringToByteArray ("00 76"); // 9F1A 02 ;Brazil byte[] bufTVR = hexStringToByteArray ("00 00 00 08 00"); // 95 05 ;Merchant forced transaction to go online byte[] bufTxCurrencyCode = hexStringToByteArray ("09 86"); // 5F2A 02 ;Brazilian Reals (BRL) byte[] bufTxDate = getCurrentDateAsByteArray("yyMMdd"); // 9A 03 ;04.SET.2014 byte[] bufTxType = hexStringToByteArray ("00"); // 9C 01 ;Goods & services byte[] bufUN = new byte[4]; RandomNumberGenerator.nextBytes(bufUN); // 9F37 04 ;Unpredictable Number System.out.println( " [Transaction Parameters]"); System.out.println( " AmountAuthorized= "+ toHexString( bufAmountAuthorized ) ); System.out.println( " AmountOther= "+ toHexString( bufAmountOther ) ); System.out.println( " TCC= "+ toHexString( bufTermCountryCode ) ); System.out.println( " TVR= "+ toHexString( bufTVR ) ); System.out.println( " TxDate= "+ toHexString( bufTxDate ) ); System.out.println( " TxType= "+ toHexString( bufTxType ) ); System.out.println( " UN= "+ toHexString( bufUN ) ); System.out.println( ""); byte[] bufTxData = concat ( bufAmountAuthorized, bufAmountOther, bufTermCountryCode, bufTVR, bufTxCurrencyCode, bufTxDate, bufTxType, bufUN ); System.out.println( "GenerateAC (TxData= " + toHexString( bufTxData ) + ")" ); answer = channel.transmit( new CommandAPDU( 0x80, 0xAE, 0x40, 0x00, bufTxData ) ); System.out.println( printAPDU( answer ) ); System.out.println( "" ); // get 9F27, 9F36, 9F10, 9F26 byte[] bufResponse = Arrays.copyOfRange( answer.getData(), 2, answer.getData().length ); byte[] bufBit55 = concat( hexStringToByteArray ("82 02"), bufAIP, hexStringToByteArray ("84 07"), bufAID, hexStringToByteArray ("9F1A 02"), bufTermCountryCode, hexStringToByteArray ("95 05"), bufTVR, hexStringToByteArray ("9C 01"), bufTxType, hexStringToByteArray ("9F37 04"), bufUN, bufResponse ); if ( boolEverythingAsTLV ) { bufBit55 = concat( hexStringToByteArray ("5F34 01"), bufPANSequence, hexStringToByteArray ( "5A 08"), bufPAN, //hexStringToByteArray ("5F34 01"), bufPANSequence, hexStringToByteArray ("9F02 06"), bufAmountAuthorized, hexStringToByteArray ("9F03 06"), bufAmountOther, hexStringToByteArray ("5F2A 02"), bufTxCurrencyCode, hexStringToByteArray ( "9A 03"), bufTxDate, bufBit55 ); } System.out.println( "Bit55 = "); System.out.println( toHexString( bufBit55 ) ); System.out.println( ); // Disconnect the card card.disconnect(false); /* // Send to host if ( args.length >= 4 ) { String sHost = args[2]; int iPort = Integer.parseInt( args[3] ); SendISOPacket( sHost, iPort, bufPAN, bufAmountAuthorized, bufAmountOther, bufTxCurrencyCode, bufTxDate, bufExpiryDate, bufTermCountryCode, bufTrack2, bufBit55 ); } */ System.exit(0); } catch(Exception e) { System.out.println( "#ERROR#: " + e.toString() ); System.exit(6); } } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值