Search code examples
md5smartcardjavacardapdumessage-digest

How can I generate an MD5 hash on Java Card 2.2.1?


I'm trying to hash an 8-byte message (maybe need to enlarge it to 128) using Java Card which supports MD5. This is my source code:

package net.sourceforge.globalplatform.jc.helloworld;
import javacard.framework.*;
import javacard.security.*;
import javacardx.crypto.Cipher;

import javax.print.attribute.standard.MediaSize;
import java.util.logging.Level;

public class HelloWorldApplet extends Applet {

final static byte  APPLET_CLA    = (byte)0x80;
final static byte  HASH          = (byte)0x05;

public static byte[] Message;

MessageDigest mDig = MessageDigest.getInstance(MessageDigest.ALG_MD5, true);

public static void install(byte[] bArray, short bOffset, byte bLength)
{
    Message = new byte[256];
    new HelloWorldApplet().register(bArray, (short) (bOffset + 1), bArray[bOffset]);
}

public void process(APDU apdu)
{
    if (selectingApplet())
    {
        return;
    }

    byte[] buffer = apdu.getBuffer();
    if (buffer[ISO7816.OFFSET_CLA] == APPLET_CLA) {

        switch (buffer[ISO7816.OFFSET_INS]) {

            case HASH:
                hash_message(apdu);
                break;

            default:
                ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
        }
    } else {
        ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
    }
}

public void hash_message(APDU apdu) {
    byte[] buffer = apdu.getBuffer();
    short mLen = apdu.setIncomingAndReceive();
    mDig.reset();
    mDig.doFinal(buffer, (short) ISO7816.OFFSET_CDATA, mLen, Message, (short) 0);
    Util.arrayCopy(Message,(short)0,buffer,(short)0, mLen);
    apdu.setOutgoingAndSend((short)0,mLen);
}

}

And this is also my tests using GPShell:

send_apdu -sc 1 -APDU 80050000081122334455667788
Command --> 80050000081122334455667788
Wrapped command --> 80050000081122334455667788
Response <-- DD254CDC958E53AB9000
send_APDU() returns 0x80209000 (9000: Success. No error.)

I have different questions:

  1. I tested my data on MD5 hash using this link and I got that it is not working correctly! I don't know why this algorithm does not respond correctly while I am using a code as simple as possible! can anyone tell me the problem?

  2. Is there any way to send/receive 256 Bytes of data to/from the card?

  3. What is a better substitute for GPShell?


Update 1: I have seen this link and it did not solve my problem.

Update 2: Answer to question 1 returns to Hex and ASCII data difference in GPShell and the online calculator.


Solution

  • The MD5 hash differs from the one generated though some online tool. Where is the problem?

    There are two problems that cause differences between the hash generated in the online tool and the hash generated in the applet:

    1. The first problem is the input data format. The online tool that you used (http://www.xorbin.com/tools/md5-hash-calculator) treats the input as ASCII string. Hence, if you input the ASCII string "1122334455667788", you'll get the hash value 8a1bb284d84b7e7df32cba6d8e89eac9 (hexadecimal number). However, the data you hash in the applet is not the ASCII string "1122334455667788" (it's hexadecimal repesentation would be 31313232333334343535363637373838). Instead, you hash the hexadecimal number 1122334455667788. This results in the MD5 hash dd254cdc958e53abaa67da9f797125f5. You can check this with this online calculator: http://www.fileformat.info/tool/hash.htm?hex=1122334455667788.

    2. The second problem is the length of the hash value that you return from your applet. You only return mLen bytes (the size of the input value) instead of the full length of the hash value. An MD5 hash alsways has 128 bits (16 bytes). Consequently, you would typically want to return all 16 bytes from your applet:

      mDig.doFinal(buffer, (short)ISO7816.OFFSET_CDATA, mLen, buffer, (short)0);
      apdu.setOutgoingAndSend((short)0, (short)16);
      

      Note that there is no need to use an intermediate byte array (and particularly not a static one) as MessageDigest.doFinal() supports using the same array for input and output even if ranges overlap.

    Is there any way to send/receive 256 Bytes of data to/from the card?

    If your card supports extended length APDUs, you could use them to trasfer more than 255 bytes of data in one command APDU. When not using extended length APDUs, the typical approach would be to split the input data across multiple APDUs. You could, for instance, use P2 to distinguish between the first, intermediate, and last command APDUs:

    public void hash_message(APDU apdu) {
        byte[] buffer = apdu.getBuffer();
        byte p2 = buffer[ISO7816.OFFSET_P2];
    
        if (p2 != (byte)0x02) {
            if (p2 == (byte)0x01) {
                mDig.reset();
            }
    
            short bytesLeft = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
            short readCount = apdu.setIncomingAndReceive();
            while (bytesLeft > 0) {
                mDig.update(buffer, (short)ISO7816.OFFSET_CDATA, readCount);
                bytesLeft -= readCount;
                readCount = apdu.receiveBytes((short)ISO7816.OFFSET_CDATA);
            }
        } else {
            mDig.doFinal(buffer, (short)ISO7816.OFFSET_CDATA, (short)0, buffer, (short)0);
            apdu.setOutgoingAndSend((short)0, (short)16);
        }
    }
    

    You can then start generating a new hash value by sending the APDU:

    80 05 0001 08 1122334455667788
    

    You can continue to feed more data into the hash generation using APDUs like this:

    80 05 0000 Lc DATA
    

    Finally, you can compute the resulting hash using an APDU of the form:

    80 05 0002 00
    

    What is a better substitute for GPShell?

    That very much depends on what you want to achieve. Other tools that allow you to send APDUs to a smartcard are, e.g. GScriptor or opensc-tool. You could also create your own application that sends APDUs through a PC/SC API (e.g. Java Smartcard API in Java).