在真正的智能卡上读取记录失败

read records failed on real smart card

我开发了一个小程序,当它从主机应用程序接收到读取记录命令时,它 return 一些模拟字节数据(6 字节)。 我用 JCIDE 测试了 applet,它运行良好。 我还使用 pyApdutool 使用虚拟卡 reader 对其进行了测试,它也运行良好。

但是当我使用pyApdutool 在卡片上安装小程序时,使用相同的Read Records 命令,卡片return "Unknown error" 消息。 但是当我没有设置数据响应时,卡return“90 00”消息。

 apdu.setOutgoingAndSend((short) 0, (short)0) 

小程序安装成功,使用pyApdutool可以select小程序上卡

这是读取记录命令: [0x00 0xB3 0x00 0xFF]

我使用 etOutgoingAndSend() 方法发送数据

apdu.setOutgoingAndSend((short) 0, (short)6) 

Here is testing result with JCIDE

Here is testing result with PyApduTool on real card

这是我的代码:

package PTCRecords;

import javacard.framework.*;

public class PTCRecords extends Applet {



    // class of instructions
    private final static byte CLA = (byte) 0x00;


    // instruction codes
    private final static byte INS_SELECT        = (byte) 0xA4;  // select
    private final static byte INS_READ_R        = (byte) 0xB2;  // read record
    private final static byte INS_READ_RS       = (byte) 0xB3;  // read records


    // instruction parameters
    private final static byte P1_SEL_DF         = (byte) 0x04;  // select DF
    private final static byte P2_SEL_DF         = (byte) 0xC0;  // 
    private final static byte P1_SEL_EF         = (byte) 0x02;  // select EF
    private final static byte P2_SEL_EF         = (byte) 0xC0;  // 
    private final static byte P1_RD_R16         = (byte) 0x10;  // read record #16
    private final static byte P1_RD_R8          = (byte) 0x08;  // read record #4
    private final static byte P1_RD_R5          = (byte) 0x05;  // read record #1
    private final static byte P2_RD_R           = (byte) 0x04;  // 
    private final static byte P1_RD_RS          = (byte) 0x00;  // read records
    private final static byte P2_RD_RS          = (byte) 0xFF;  // 

    // EF No.
    private final static byte NO_EF0            = (byte) 0x00;  // DF
    private final static byte NO_EF1            = (byte) 0x01;  // EF1
    private final static byte NO_EF2            = (byte) 0x02;  // EF2
    private final static byte NO_EF3            = (byte) 0x03;  // EF3
    private final static byte NO_EF4            = (byte) 0x04;  // EF4
    private final static byte NO_EF5            = (byte) 0x05;  // EF5
    private final static byte NO_EF6            = (byte) 0x06;  // EF6
    private final static byte NO_EF7            = (byte) 0x07;  // EF7

    //only for test
    private final static short ARRAY_SIZE = 256;

    private byte[] outBuffer;
    private byte[]   currentEF; 


    /**
     * Constructor
     */
    private PTCRecords(byte[] bArray, short bOffset, byte bLength) {
        currentEF = JCSystem.makeTransientByteArray((short)1, JCSystem.CLEAR_ON_DESELECT);
        currentEF[0] = NO_EF0;  

        // create a transient buffer
        outBuffer = new byte[ARRAY_SIZE];


    }


    public static void install(byte[] bArray, short bOffset, byte bLength) 
    {
        new PTCRecords(bArray, bOffset, bLength).register(bArray, (short) (bOffset + 1), bArray[bOffset]);

    }

    public boolean select() {


        return true;
    } // select

    public void process(APDU apdu) {
        //Insert your code here
        byte[] buf = apdu.getBuffer();

        // the selectingApplet() is used in the applet process method to distinguish
        // the SELECT APDU command, which selected this applet, from all other SELECT
        // APDU commands. Returns true if this applet is being selected
        if (selectingApplet()) {
            currentEF[0] = NO_EF0;  // hirose 150127
            ISOException.throwIt(ISO7816.SW_NO_ERROR);
            return;
        }

        // verify if the applet can accept this APDU message
        if (buf[ISO7816.OFFSET_CLA] != CLA) {
            ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
        }
        switch (buf[ISO7816.OFFSET_INS]) {
            case INS_SELECT:
                    selectEF(apdu);
                break;
            case INS_READ_R:  // 0xB2: read record
                ReadRecord(apdu);
                break;

            case INS_READ_RS:  // 0xB3: read records
                 //0x00 0xB3 0x00 0xFF 
                if((buf[ISO7816.OFFSET_P1] == P1_RD_RS)
                    && (buf[ISO7816.OFFSET_P2] == P2_RD_RS))
                {   // 0x00FF: ReadRecords
                    ReadRecors(apdu);
                } else {
                    ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
                }
                break;
            default:
                ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
        }
    }

    private void selectEF(APDU apdu) {
        // select
        byte buffer[] = apdu.getBuffer();
        currentEF[0] = buffer[ISO7816.OFFSET_CDATA+1];  

        ISOException.throwIt(ISO7816.SW_NO_ERROR);
    }

    private void ReadRecord(APDU apdu) {
        // ReadRecord
        byte buffer[] = apdu.getBuffer();
        byte len=0;
        byte record=0;
        byte cnt=0;
        len = buffer[ISO7816.OFFSET_LC];
        record = buffer[ISO7816.OFFSET_P1];

        switch(currentEF[0]){   
        case NO_EF1:    // EF#1
        case NO_EF4:    // EF#4
        case NO_EF7:    // EF#7
            if( record > P1_RD_R16){
                ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
            }
            break;
        case NO_EF2:    // EF#2
        case NO_EF5:    // EF#5
            if( record > P1_RD_R8){
                ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
            }
            break;
        case NO_EF3:    // EF#3
        case NO_EF6:    // EF#6
            if( record > P1_RD_R5){
                ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
            }
            break;
        }

        // set data
        getData( buffer, (byte)(0), (byte)len , currentEF[0]);  

        // send data
        apdu.setOutgoingAndSend((short) 0, (short) len);
    }

    private void ReadRecors(APDU apdu) {
        byte buffer[] = apdu.getBuffer();
        byte lc = 0;
        byte idxLen = 0;
        byte len = 0;
        byte cnt = 0;
        if( buffer[ISO7816.OFFSET_CDATA] != 0x53){
                ISOException.throwIt(ISO7816.SW_WRONG_DATA);
        }

        lc = buffer[ISO7816.OFFSET_LC];
        idxLen = buffer[ISO7816.OFFSET_CDATA+1];
        if( lc != idxLen+2){
               ISOException.throwIt(ISO7816.SW_WRONG_DATA);
        }

        for( cnt = 0;cnt<idxLen;cnt+=4){
            switch(buffer[(byte)(ISO7816.OFFSET_CDATA+cnt+3)]){
            case NO_EF1:    // EF#1
            case NO_EF4:    // EF#4
            case NO_EF7:    // EF#7
                len += 5;
                break;
            case NO_EF2:    // EF#2
            case NO_EF5:    // EF#5
                len += 8;
                break;
            case NO_EF3:    // EF#3
            case NO_EF6:    // EF#6
                len += 16;
                break;
            }
        }

        getData( buffer, (byte)(0), (byte)5 , (byte)1);
        getData( buffer, (byte)(5), (byte)8 , (byte)2);
        getData( buffer, (byte)(5+8), (byte)16 , (byte)3);
        getData( buffer, (byte)(5+8+16), (byte)5 , (byte)4);
        getData( buffer, (byte)(5*2+8+16), (byte)8 , (byte)5);
        getData( buffer, (byte)(5*2+8*2+16), (byte)16 , (byte)6);
        getData( buffer, (byte)(5*2+8*2+16*2), (byte)5 , (byte)7);


        apdu.setOutgoingAndSend((short)0, (short)6);
    }
    private void getData( byte buffer[], byte offset, byte len , byte val) {
        byte cnt;
        for( cnt = offset; cnt < offset + len ; cnt++){
            buffer[cnt] = val;

        }
    }

}

如果可以的话,请尝试告诉我为什么它不能在真卡上运行? 谢谢。

ReadRecors()方法的最开头使用setIncomingAndReceive。喜欢:

 private void ReadRecord(APDU apdu) {
    // ReadRecord
     apdu.setIncomingAndReceive();
     rest of your codes....

调用此方法表示此APDU有传入数据。

问题是您的 JCIDE 模拟器表现为 T=1 卡,但您的真实卡是 T=0 卡。 T=0T=1是ISO/IEC7816-3标准定义的传输协议。 T=0 是更老更简单的。

T=1 returns 所有数据连同状态字独立在 APDU 中的 Le 字节上。

T=0 检查输出数据的长度是否等于输入的 Le 字节(T=0 卡预计您的设备内存有限,因此它会响应尽可能多的数据仅按要求输出字节)。如果不是,它会发送状态字 6CXX,其中 XX 是正确的 Le 字节。您必须使用 Le=XX:

重新发送命令
<- 00 A4 04 00 06 11 22 33 44 55 66 00
-> 90 00

<- 00 B3 00 FF
-> 6C 06

<- 00 B3 00 FF 06
-> 11 11 11 11 11 22 90 00

您不必担心性能 - 命令不会再次处理。您的输出在第一次通话时就已准备就绪,您只需提出要求即可。