9

I'm trying to authenticate DESFire card with my android application. I use the example in this link to decypher the bytes I got from the card. For that, I ruled out padding in decryption (commented out below), because DESFire documentation points it out. Also, if I don't do so, decryption returns 7 bytes for input of 8 bytes. Below are DES and TripleDES decryption functions I use:

public static byte[] TripleDES_Decrypt(byte[] data,byte[][] keys)
{
    int i;
    byte[] tmp = new byte[data.length];
    byte[] bloc = new byte[8];

    K = generateSubKeys(keys[0]);
    K1 = generateSubKeys(keys[1]);
    K2 = generateSubKeys(keys[2]);

    for (i = 0; i < data.length; i++) {
        if (i > 0 && i % 8 == 0) {
            bloc = encrypt64Bloc(bloc,K2, true);
            bloc = encrypt64Bloc(bloc,K1, false);
            bloc = encrypt64Bloc(bloc,K, true);
            System.arraycopy(bloc, 0, tmp, i - 8, bloc.length);
        }
        if (i < data.length)
            bloc[i % 8] = data[i];
    }
    bloc = encrypt64Bloc(bloc,K2, true);
    bloc = encrypt64Bloc(bloc,K1, false);
    bloc = encrypt64Bloc(bloc,K, true);
    System.arraycopy(bloc, 0, tmp, i - 8, bloc.length);


    //tmp = deletePadding(tmp);

    return tmp;
}

public static byte[] decrypt(byte[] data, byte[] key) {
    int i;
    byte[] tmp = new byte[data.length];
    byte[] bloc = new byte[8];

    K = generateSubKeys(key);

    for (i = 0; i < data.length; i++) {
        if (i > 0 && i % 8 == 0) {
            bloc = encrypt64Bloc(bloc,K, true);
            System.arraycopy(bloc, 0, tmp, i - 8, bloc.length);
        }
        if (i < data.length)
            bloc[i % 8] = data[i];
    }
    bloc = encrypt64Bloc(bloc,K, true);
    System.arraycopy(bloc, 0, tmp, i - 8, bloc.length);

    //tmp = deletePadding(tmp);

    return tmp;
}

According to DesFire document, I need two modes of decryption, send and receive. This blog post has some explanation about it.

However, the DESFire crypto is a bit different from the normal DES/CBC scheme: The PCD uses DES “send mode” when sending data (xor before DES), and the card uses DES “recieve mode” when recieving data (xor after DES). But when the PCD recieves data, it uses normal DES/CBC mode (xor after DES), and the card uses normal DES send mode when sending data (xor before DES).

And in Android side I follow the examples and recommendations:

// connected to tag and application     

// result = encoded(randB) + af 
byte[] result = idTag.transceive(Utils.wrapMessage((byte)0x0a, new byte[]{(byte)0x0}));

byte[] b0 = new byte[8];
for(int i = 0; i < 8; i++) {
    b0[i] = result[i];
}

// key
byte[] key = new byte[] {(byte)0x0,(byte)0x0,(byte)0x0,(byte)0x0,
                 (byte)0x0,(byte)0x0,(byte)0x0,(byte)0x0,
                 (byte)0x0,(byte)0x0,(byte)0x0,(byte)0x0,
                 (byte)0x0,(byte)0x0,(byte)0x0,(byte)0x0 };
byte[][] keys = new byte[3][];
keys[0]=key; keys[1]=key; keys[2]=key;

// decrypt encoded(randB)
byte[] r0 = DES.TripleDES_Decrypt(b0, keys);

// generate randA (integer 0-7 for trying) 
byte[] nr = new byte[8];
for(int i = 0; i < 8; i++) {
    nr[i] = Byte.parseByte(Integer.toString(i), 16);
}
// decrypt randA
byte[] b1 = DES.TripleDES_Decrypt(nr, keys);

// shift randB and get randB'
byte[] r1 =new byte[8];
for(int i = 0; i < 7; i++) {
    r1[i] = r0[i + 1];
}
r1[7]=r0[0];

// concat (randA + randB')
byte[] b2 = new byte[16];
for(int i = 0; i < 16; i++)
{
    if(i <= 7) {
    b2[i] = b1[i];
} else {
    b2[i] = r1[i - 8];
}
}

// XOR (randA + randB') with IV
// IV is told to be consisting of 0's, 
// but XOR something with 0 results the same? 
for(int i=0;i<16;i++) {
    b2[i] = (byte) (b2[i] ^ (byte)0x0);
}

// send AF and decrypt(A+B) 
// wrap message adds needed wrapping to message (90 to left, offset bytes etc.)
result = isodepTag.transceive(Utils.wrapMessage((byte)0xaf, DES.TripleDES_Decrypt(b2, keys)));

I get the first result, the encrypted randB. However, the second "result" is always "91ae", means authentication error. I'm doing something wrong here, send wrong data to card.

Can anyone tell me what must I change in the code to work in these modes? What should I XOR with data before/after TripleDES?

Not the real question, but I read that default "Key" in DesFire card is 16 zero bytes. Also the document points that I need to use TripleDES for 16 bytes of key, DES for 8 bytes of key. So I'm using and need to use TripleDES as I haven't changed the default key, am I right?

For those who need the know about CipherBlockChaining.

EDIT: I found out that I need to do XORing before and after TripleDES and I mustn't touch TripleDES's internal operations at all. I will be trying that in a while.

Deleted the inner TripleDES lines, just saying for the ones seeing the question for the first time.

İsmet Alkan
  • 5,141
  • 3
  • 38
  • 64
  • I remember a colleague doing DESFire, and I don't think there was anything special about the way it handled TDES. I do remember that the IV was "chained" between the command and response: the last block of the command was used as the IV for the response and vice versa. Just a remark, not an answer, as I don't have a DESFire to play with at the moment. – Maarten Bodewes Jan 02 '13 at 23:55
  • Thanks, I will consider that. – İsmet Alkan Jan 02 '13 at 23:59

2 Answers2

10

OK I got the solution. My mistake was that I was sending

3DES(randA + randB') 

But I should send

3DES(randA) + 3DES(randB' XOR 3DES(randA))

Here's the authentication code for Android/Java (it's so sad that this is the only one that can be found on the net currently!):

The actual authentication code:

// send initial authentication request
byte[] result = idTag.transceive(Utils.wrapMessage((byte)0x0a, new byte[]{(byte)0x0}));

// get encrypted(randB) from the response
byte[] b0 = new byte[8];
for(int i = 0; i < 8; i++) {
    b0[i] = result[i];
}

// 16 bytes default key
byte[] key = new byte[] {(byte)0x0,(byte)0x0,(byte)0x0,(byte)0x0,
    (byte)0x0,(byte)0x0,(byte)0x0,(byte)0x0,
    (byte)0x0,(byte)0x0,(byte)0x0,(byte)0x0,
    (byte)0x0,(byte)0x0,(byte)0x0,(byte)0x0 };
// keys for TripleDes
byte[][] keys = new byte[3][];
keys[0] = key; keys[1] = key; keys[2] = key;

// decrypt encoded(randB)
byte[] r0 = DES.TripleDES_Decrypt(b0, keys);

// generate randA (integer 0-7 for trying, should randomize for real-life use) 
byte[] nr = new byte[8];
for(int i = 0; i < 8; i++) {
    nr[i] = Byte.parseByte(Integer.toString(i), 16);
}

// decrypt randA, should XOR with IV, but IV is all 0's, not necessary
byte[] b1 = DES.TripleDES_Decrypt(nr, keys);

// shift randB one byte left and get randB'
byte[] r1 =new byte[8];
for(int i = 0; i < 7; i++) {
    r1[i] = r0[i + 1];
}
r1[7]=r0[0];

// xor randB' with randA and decrypt
byte[] b2 = new byte[8];
for(int i = 0; i < 8; i++) {
    b2[i] = (byte) (b1[i] ^ r1[i]);
}
b2 = DES.TripleDES_Decrypt(b2, keys);

// concat (randA + randB')
byte[] b1b2 = new byte[16];

for (int i = 0; i < b1b2.length; i++) {
    if(i <= 7) {
        b1b2[i] = b1[i];
    } else {
        b1b2[i]=b2[i-8];
    }
}

result = idTag.transceive(Utils.wrapMessage((byte)0xaf, b1b2));

TripleDes is the one in the question. wrapMessage function:

public static byte[] wrapMessage (byte command, byte[] parameters) throws Exception {
    ByteArrayOutputStream stream = new ByteArrayOutputStream();

    stream.write((byte) 0x90);
    stream.write(command);
    stream.write((byte) 0x00);
    stream.write((byte) 0x00);
    if (parameters != null) {
        stream.write((byte) parameters.length);
        stream.write(parameters);
    }
    stream.write((byte) 0x00);

    return stream.toByteArray();
}

EDIT: Thanks to VGe0rge, we found out the reason why this authentication doesn't work from time to time. Instead of calling the 3DES function in the question, just call:

Cipher.getInstance("DESede/CBC/NoPadding");
İsmet Alkan
  • 5,141
  • 3
  • 38
  • 64
  • 2
    Thanks for reporting back. Note that `3DES(randA) + 3DES(randB' XOR 3DES(randA))` simply means `TDES_CBC(randA | randB)` with a zero IV. – Maarten Bodewes Jan 05 '13 at 01:34
  • The real reason I'm doing DesFire authentication is to learn cryptology. I want to be able to modify and understand the ciphering operation, the example in my link provides all the information and implementation for that. – İsmet Alkan Jan 05 '13 at 04:03
  • Can you please post code for TripleDES_Decrypt as I have converted it to C#. – Syed Muhammad Mubashir Aug 19 '15 at 16:34
  • 1
    Be sure to use Cipher.getInstance("DESede/CBC/NoPadding","BC") on Marshmallow or it will stop working. See my [Question](http://stackoverflow.com/questions/34286798) – Nublodeveloper Dec 16 '15 at 09:01
0

Ismat, did you try to write data to the DESFIRE files?

As you explained, when sending data to the card: The PCD uses DES “send mode” when sending data (xor before DES), and the card uses DES “recieve mode” when recieving data (xor after DES)

So I can't get the proper code in order to implement the TDES with the XOR before..I need to do all the crypt-decrypt-crypt and the process is to slow for my application:

res = criptoTransformDec.TransformBlock(datosEscribir, 0, 8, datosEscribir, 0);
res = criptoTransformEnc.TransformBlock(datosEscribir, 0, 8, datosEscribir, 0);
res = criptoTransformDec1.TransformBlock(datosEscribir, 0, 8, datosEscribir, 0);

int l_iAux = 0;
while (l_iAux < (datosEscribir.Length - 8))
    {
        criptoTransformDec2 = desDec.CreateDecryptor(claveSes1, tdesInitialVector);
        //desEnc2 = new DESCryptoServiceProvider();
        criptoTransformEnc2 = desEnc.CreateEncryptor(claveSes2, tdesInitialVector);
        //desDec3 = new DESCryptoServiceProvider();
                criptoTransformDec3 = desDec.CreateDecryptor(claveSes1, tdesInitialVector);

        Array.Copy(datosEscribir, 8 + l_iAux, aux1, 0, 8);
        Array.Copy(datosEscribir, l_iAux, aux2, 0, 8);
        DesfireBarik.XorStr(ref aux1, ref aux2, 8);
        res = criptoTransformDec2.TransformBlock(aux1, 0, 8, datosEscribir, 8 + l_iAux);
        res = criptoTransformEnc2.TransformBlock(datosEscribir, 8 + l_iAux, 8, datosEscribir, 8 + l_iAux);
        res = criptoTransformDec3.TransformBlock(datosEscribir, 8 + l_iAux, 8, datosEscribir, 8 + l_iAux);

        l_iAux += 8;
    }

private static void XorStr (ref byte[] str1, ref byte[] str2, int qty )
    {
        int i = 0;
        for (i = 0; i < qty; i++ )
            str1[i] = (byte)(str1[i] ^ str2[i]);
    }
wmk
  • 4,531
  • 1
  • 18
  • 37