tecogill
tecogill

Reputation: 86

How to write a file in a smart card with java card

Hello i'm a beginner on the use of javacard ! I want to write a file (less than 1024kb) in my smart card so what i do is to transform the file to an array of byte (byte[]), for example i have an array with the length of 638 ! My code to write and read the data works fine (no error) but when i read the data from the smart card and compare it to the default array, they are not same (same length but different content). I read a lot of topic here but i was unable to find one which can solve my problem ! Thank you for help

I use JCDK 2.2.2

My applet that read and write data:

.....
public void process(APDU apdu) {
    byte[] buffer = apdu.getBuffer();
    switch(buffer[ISO7816.OFFSET_INS]) {
        case INS_SET_DATA:
            // byte bufferDataLength = (byte)(apdu.setIncomingAndReceive());

            //We want to start data copy
            if (buffer[ISO7816.OFFSET_P1] == 0x00 && buffer[ISO7816.OFFSET_P2] == 0x00) {
                data = new byte[(short) 638];
            } else {
                // copying the apdu data into byte array Data
                // array copy: (src, offset, target, offset,copy size)
                Util.arrayCopyNonAtomic(buffer, (short) ISO7816.OFFSET_CDATA, data,
                            (short) ((short) buffer[ISO7816.OFFSET_P1] * 100),
                            (short) buffer[ISO7816.OFFSET_P2]
                    );
                }
            break;
        case INS_GET_DATA:
            // array copy: (src, offset, target, offset,copy size)
            short t = (short)(buffer[ISO7816.OFFSET_P2] & 0xFF);
            short o = (short)(buffer[ISO7816.OFFSET_P1] & 0xFF);

            Util.arrayCopyNonAtomic(data, o, buffer, ISO7816.OFFSET_CDATA, t);
            apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, t);
            break;
    }
}
.....

The code i use to send the data to the smartcard (apduio package of JCDK 2.2.2)

.....

switch(choice) {
   case 8:
       byte[] configData = fileToByteArray("D:\\jcard\\config.dat");
       byte[] chunk = new byte[100];

       System.out.println("Config Length: " + configData.length); // Got 638

       int configLength = configData.length;
       double round = Math.floor((double)configLength / 100);
       int lastPart = configLength % 100;

       System.out.println("Round : " + round); // Got 6
       System.out.println("Last Part : " + lastPart); // Got 38

       double partToSend = lastPart == 0 ? round : round + 1;

       //Initialize the array of byte in my applet to have the length of data i want to write in
       apdu.command[Apdu.INS] = MyAppletClient.INS_SET_DATA;
       apdu.command[Apdu.P1] = 0x00;
       apdu.command[Apdu.P2] = 0x00;

       cad.exchangeApdu(apdu);
       handleResponse(apdu);

       boolean allPartSend = true;
       for (int i = 0; i < round; i++) {
           //array copy: (src, offset, target, offset, copy size)
           System.arraycopy(configData, (i * 100), chunk, 0, 100);
           System.out.println("Data Length: " + chunk.length); // Got 100

           apdu.command[Apdu.P1] = (byte) i;
           apdu.command[Apdu.P2] = 100;

           apdu.setDataIn(Data);
           cad.exchangeApdu(apdu);
           if (apdu.getStatus() != 0x9000) {
               System.out.println("["+i+"] An error occurred with status: " + apdu.getStatus());
               allPartSend = false;
               break;
            }
        }

        if(allPartSend) {
            byte[] finalPart = new byte[lastPart];
            System.arraycopy(configData, (int) (round * 100), finalPart, 0, lastPart);

            apdu.command[Apdu.P1] = (byte) round;
            apdu.command[Apdu.P2] = (byte) lastPart;
            apdu.setDataIn(finalPart);
            cad.exchangeApdu(apdu);

            if (apdu.getStatus() != 0x9000) {
                System.out.println("An error occurred with status: " + apdu.getStatus());
                break;
            } else {
                System.out.println("OK");
            }
        } else {
            System.out.println("Fail to send all data");
        }
        break;
    case 9:
        int cfgLength = 638; //Because i know the array has that length
        byte[] res = new byte[cfgLength];
        double rnd = Math.floor((double)cfgLength / 100);
        int last = fpLength % 100, readLength = 0;

        boolean allSend = true;

        for(int i = 0; i < rnd; i++) {
            apdu.command[Apdu.INS] = MyAppletClient.INS_GET_DATA;
            apdu.command[Apdu.P1] = (byte) (i * 100);
            apdu.command[Apdu.P2] = 100;
            cad.exchangeApdu(apdu);

            if (apdu.getStatus() != 0x9000) {
                System.out.println("An error occurred with status: " + apdu.getStatus());
            } else {
                readLength += apdu.dataOut.length;
                // System.out.println("Datalength : " + apdu.dataOut.length); // Got 100
                //array copy: (src, offset, target, offset, copy size)
                System.arraycopy(apdu.dataOut, 0, res, (i*100), apdu.dataOut.length);
            }
         }

         if(allSend) {
             apdu.command[Apdu.INS] = MyAppletClient.INS_GET_DATA;
             apdu.command[Apdu.P1] = (byte) ((int)rnd * 100);
             apdu.command[Apdu.P2] = (byte) last;

             cad.exchangeApdu(apdu);
             if (apdu.getStatus() != 0x9000) {
                 System.out.println("An error occurred with status: " + apdu.getStatus());
                 break;
             } else {
                 readLength += apdu.dataOut.length;
                 //array copy: (src, offset, target, offset, copy size)
                 System.arraycopy(apdu.dataOut, 0, res, (int)(rnd * 100 ), apdu.dataOut.length);          
             }
         } else {
              System.out.println("Fail to get all the part");
         }

         byte[] cfgData = fileToByteArray("D:\\jcard\\config.dat");
         System.out.println("ReadLength : " + readLength); // Got 638
         System.out.println("Array equals : " + Arrays.equals(cfgData, res)); // Got false but expected true

         break;
     }
}

.....

Upvotes: 2

Views: 1885

Answers (2)

Michal Iwanicki
Michal Iwanicki

Reputation: 113

My guess is that the reason why you are receiving different data from the one you wrote to the card is this line of code:

switch(choice) {
...
    case 9:
    ...
        for(int i = 0; i < rnd; i++) {
        ...
            apdu.command[Apdu.P1] = (byte) (i * 100);

Unlike in your code for writing data to the card, here you are passing the data offset in P1 parameter. Please remember, that P1 is only one byte and it will overflow for i=3, so it is not possible to encode offsets of 638-bytes long array in P1.

The quickest solution of your problem is to use the same approach as in your writing code, i.e.:

switch(choice) {
...
    case 9:
    ...
        for(int i = 0; i < rnd; i++) {
        ...
            apdu.command[Apdu.P1] = (byte) i;

and do the multiplication in the JavaCard code:

short o = (short) ((short) buffer[ISO7816.OFFSET_P1] * 100);

This solution should work, but it is not perfect as your JavaCard code supports arbitrary length of read/write (P2 - copy length), but offsets are fixed to 0, 100, 200 etc. It means that it works only as long as you send P2=100 to the card, which your present code does. But then why sending this parameter in the first place if it is fixed to one value?

I agree with Maarten Bodewes that it would be best to follow the ISO/IEC 7816-4 and use the command syntax specified there. UPDATE BINARY and READ BINARY commands described in the standard should fit your needs.

Upvotes: 1

Maarten Bodewes
Maarten Bodewes

Reputation: 93948

It looks like you are mixing ISO case 1 and 3 commands. An ISO case 1 means that no CDATA or RDATA is present, and ISO case 3 means just CDATA, no RDATA. Generally it is best to let a command be only one of the possible 4 cases (case 2 is just RDATA and case 4 both CDATA and RDATA. For instance, you can create a CREATE RECORD command or something similar.

You should call setIncomingAndReceive right before copying the data to the array. If you do not do this then only part of the data may be present in the buffer. Note though that setIncomingAndReceive is a convenience method, and you may be able to find better methods for handling large amounts of data.

Note that ISO 7816-4 already defines a file based card system; copying part of that existing standard may be more useful than creating your own from scratch. Also note that you should prefer APDU#getOffsetCdata rather than the constant. That method is compatible with extended length APDU's, and you may want to upscale to those in the future.

Upvotes: 3

Related Questions