39
votes

I have been trying for more than a week to make an Android phone authenticate with a Mifare Ultralight C. I have confirmed I can write to the tag (by writing to an unsecured memory page and then reading what I wrote). I can also write to the key pages (44-47) and have written 0x00 for all 16 key bytes.

When I try to authenticate, the following is an example of the data involved during one exchange - it is from a log written by my application. Can anyone tell me if I am doing something incorrect? I AM under non-disclosure and have access to the full data sheets. Note that the hexadecimal strings below are obviously human readable versions of the data being sent and received, which in the code consists of byte arrays.

Send authenticate command

Received rndB: 8A5735694D9D7542

Key: 00000000000000000000000000000000

IV: 0000000000000000

Decrypted rndB: EF340C62E1B866D4

rndB': 340C62E1B866D4EF

rndA: 6E262630E299F94F

rndA+rndB': 6E262630E299F94F340C62E1B866D4EF

Key: 00000000000000000000000000000000

IV: 8A5735694D9D7542

ek(RndA+rndB'): E36C6C46FAAC60BA45DDF5F5A0802C79

After sending 0xAF + E36C6C46FAAC60BA45DDF5F5A0802C79 I immediately lose the connection with the tag. I've gone through the data sheet and read every post I can find here. I have also looked at the libfreefare code and I honestly can't figure out what I'm doing wrong.

NXP technical support had been completely unresponsive.

Any ideas? I am at a loss.

1
I found the examples in NXP Application Note AN0945 very instructive and useful for debugging my own code.NFC guy
Great, thanks! I missed that because it's a DESFire document and I didn't pay attention to that folder in the DocStore. I just downloaded and it looks promising. Thanks again - I really appreciate your input.Matt Redmond
NFCGuy - Have you (or has anyone you know of) successfully done an end-to-end authentication with an Ultralight-C using an Android device?Matt Redmond
Yes, it can certainly be done. See for example this comment and this appNFC guy
I can confirm that the NXP app actually tries to perform authentication (with the all-zero key and the key used in sample cards: "BREAKMEIFYOUCAN!"). I just checked your calculations above and they are all correct! So my first guess is that the card you test with has a different key configured. The other option is that you single-step through the code, which may cause the Android device to do a presence check for the tag in between, interfering with the authentication protocol.NFC guy

1 Answers

5
votes

Below is an example java code to perform Ultralight-C authentication as described in MF0ICU2 / MIFARE Ultralight C - Contactless ticket IC document (chapter 7.5.5 -- 3DES Authentication, page 15):

public void authenticate(byte[] key) throws CardException {
    System.out.println("AUTHENTICATE");
    byte[] encRndB = transmitRaw(new byte[] { 0x1A, 0x00 });
    if((encRndB.length!=9)||(encRndB[0]!=AF)) {
        throw new RuntimeException("Invalid response!");
    }
    encRndB=Arrays.copyOfRange(encRndB, 1, 9);
    System.out.println(" - EncRndB: " + toHex(encRndB));
    byte[] rndB = desDecrypt(key, encRndB);
    System.out.println(" - RndB: " + toHex(rndB));
    byte[] rndBrot = rotateLeft(rndB);
    System.out.println(" - RndBrot: " + toHex(rndBrot));
    byte[] rndA = new byte[8];
    generateRandom(rndA);
    System.out.println(" - RndA: " + toHex(rndA));
    byte[] encRndArotPrime = transmitRaw(ArrayUtils.addAll(new byte[] {AF}, desEncrypt(key, ArrayUtils.addAll(rndA, rndBrot))));
    if((encRndArotPrime.length!=9)||(encRndArotPrime[0]!=0x00)) {
        throw new RuntimeException("Invalid response!");
    }
    encRndArotPrime=Arrays.copyOfRange(encRndArotPrime, 1, 9);
    System.out.println(" - EncRndArot': " + toHex(encRndArotPrime));
    byte[] rndArotPrime = desDecrypt(key, encRndArotPrime);
    System.out.println(" - RndArot': " + toHex(rndArotPrime));
    if(!Arrays.equals(rotateLeft(rndA), rndArotPrime)) {
        throw new RuntimeException("Card authentication failed");
    }
}

protected static SecureRandom rnd = new SecureRandom();
protected static void generateRandom(byte[] rndA) {
    rnd.nextBytes(rndA);
}

protected byte[] desEncrypt(byte[] key, byte[] data) {
    return performDes(Cipher.ENCRYPT_MODE, key, data);
}
protected byte[] desDecrypt(byte[] key, byte[] data) {
    return performDes(Cipher.DECRYPT_MODE, key, data);
}
private byte[] iv = new byte[8];
protected byte[] performDes(int opMode, byte[] key, byte[] data) {
    try {
        Cipher des = Cipher.getInstance("DESede/CBC/NoPadding");
        SecretKeyFactory desKeyFactory = SecretKeyFactory.getInstance("DESede");
        Key desKey = desKeyFactory.generateSecret(new DESedeKeySpec(ArrayUtils.addAll(key, Arrays.copyOf(key, 8))));
        des.init(opMode, desKey, new IvParameterSpec(iv));
        byte[] ret = des.doFinal(data);
        if(opMode==Cipher.ENCRYPT_MODE) {
            iv=Arrays.copyOfRange(ret, ret.length-8, ret.length);
        } else {
            iv=Arrays.copyOfRange(data, data.length-8, data.length);
        }
        return ret;
    } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidKeySpecException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) {
        throw new RuntimeException(e);
    }
}

protected static byte[] rotateLeft(byte[] in) {
    return ArrayUtils.add(Arrays.copyOfRange(in, 1, 8), in[0]);
}

Note: this code uses Apache Commons Lang.