2

I would like to authenticate with a desfire card in native mode. Here are my current steps:

  • I select the master application (AID = 0×00 0×00 0×00)
  • I receive the response (a "challenge", randB) from the card
  • Then, in the second Step, I generate a response (randA); I concatenate the 8 bytes rand A with the 8 bytes decrypted and rotated rand B
  • I encrypt it, sending it back to the card.

I receive a 91 ae answer, and I don't know why. Here is the sequence of messages:

  • to card: 90 0A 00 00 01 00 00
  • from card 5B 57 69 C7 CE 4B 16 7B 91 AF
  • enciphered RandB = 5B 57 69 C7 CE 4B 16 7B
  • deciphered RandB' = 17 8D 23 57 10 C9 32 D5
  • one byte lshitf RandB' = 8D 23 57 10 C9 32 D5 17
  • Generated Rand A = 43 9D 17 8E 9A 5F BA 70
  • concatenate Rand A With Rand B'' = 43 9D 17 8E 9A 5F BA 70 8D 23 57 10 C9 32 D5 17
  • enciphered Rand A + Rand B'' = 9E ED DC 4F BC E7 BE BD 09 02 CF 99 F7 40 34 7B
  • to card: 90 AF 00 00 10 43 9D 17 8E 9A 5F BA 70 8D 23 57 10 C9 32 D5 17 00
  • from card: 91 AE

Please, can you point out if you see anything wrong? Which problems may I have?

Lorenzo Dematté
  • 7,638
  • 3
  • 37
  • 77
user1820817
  • 31
  • 1
  • 4

4 Answers4

2

I know the reply is a bit late, but answering it anyways - I myself was able to get the authentication right only yesterday. 91 ae stands for authentication error. That means the RandB' which you are sending to with RandA after concatination and enciphering is not as expected by the PICC (Ev1). Problem could be in the enciphering. Can you cross check your enciphering your logic with the examples in the EV1 datasheet?

Hope it helps ( if you were not able to resolve the problems already by yourself)

Vinayak Bhat
  • 341
  • 3
  • 7
1

Master key and other application authentications work with the same logic. Here is my question and answer in the subject DesFire Authentication in Android. Hope it helps.

Your problem here must be in en/deciphering. You should do CBC with 3DES or AES with no-padding.

Community
  • 1
  • 1
İsmet Alkan
  • 5,361
  • 3
  • 41
  • 64
0

In my experience, most of the time is the encryption which is done in the wrong way. Are you using the correct mode to encrypt the response? (BTW, are you using DES, TDES, or AES?). If you use DES, are you using CBC in reverse mode? IIRC, to encrypt the response you need to use the "inverse" algorithm (the one you usually do for chipering). Also, check if the keys need parity.

Lorenzo Dematté
  • 7,638
  • 3
  • 37
  • 77
0

I found a working example on how to do the authentification on the following page: https://blog.chaucery.com/2017/02/desfire-authentication-in-c.html

Also the page https://ridrix.wordpress.com/2009/09/19/mifare-desfire-communication-example/ was really helpful.

Don't forget to check this codeproject article: https://www.codeproject.com/Articles/1096861/DIY-electronic-RFID-Door-Lock-with-Battery-Backup and the examples given here: https://hack.cert.pl/files/desfire-9f122c71e0057d4f747d2ee295b0f5f6eef8ac32.html

With the help of these blogposts I was able to write the following code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace DesFireStackOverflow
{
class DesFire
{
    byte[] SessionKey = null;
    byte[] key = StringToByteArray("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00");
    byte[] initVector = StringToByteArray("00 00 00 00 00 00 00 00");

    /// <summary>
    /// Status codes (errors) returned from Desfire card
    /// </summary>
    public enum DESFireStatus
    {
        ST_Success = 0x00,
        ST_NoChanges = 0x0C,
        ST_OutOfMemory = 0x0E,
        ST_IllegalCommand = 0x1C,
        ST_IntegrityError = 0x1E,
        ST_KeyDoesNotExist = 0x40,
        ST_WrongCommandLen = 0x7E,
        ST_PermissionDenied = 0x9D,
        ST_IncorrectParam = 0x9E,
        ST_AppNotFound = 0xA0,
        ST_AppIntegrityError = 0xA1,
        ST_AuthentError = 0xAE,
        /// <summary>
        /// data did not fit into a frame, another frame will follow
        /// </summary>
        ST_MoreFrames = 0xAF,
        ST_LimitExceeded = 0xBE,
        ST_CardIntegrityError = 0xC1,
        ST_CommandAborted = 0xCA,
        ST_CardDisabled = 0xCD,
        ST_InvalidApp = 0xCE,
        ST_DuplicateAidFiles = 0xDE,
        ST_EepromError = 0xEE,
        ST_FileNotFound = 0xF0,
        ST_FileIntegrityError = 0xF1,
    }

    public enum Instructions : byte
    {
        #region DESFire legacy instructions
        DF_INS_AUTHENTICATE_LEGACY = 0x0A,
        DF_INS_CHANGE_KEY_SETTINGS = 0x54,
        DF_INS_GET_KEY_SETTINGS = 0x45,
        DF_INS_CHANGE_KEY = 0xC4,
        DF_INS_GET_KEY_VERSION = 0x64,


        DF_INS_CREATE_APPLICATION = 0xCA,
        DF_INS_DELETE_APPLICATION = 0xDA,
        DF_INS_GET_APPLICATION_IDS = 0x6A,
        DF_INS_SELECT_APPLICATION = 0x5A,


        DF_INS_FORMAT_PICC = 0xFC,
        DF_INS_GET_VERSION = 0x60,


        DF_INS_GET_FILE_IDS = 0x6F,
        DF_INS_GET_FILE_SETTINGS = 0xF5,
        DF_INS_CHANGE_FILE_SETTINGS = 0x5F,
        DF_INS_CREATE_STD_DATA_FILE = 0xCD,
        DF_INS_CREATE_BACKUP_DATA_FILE = 0xCB,
        DF_INS_CREATE_VALUE_FILE = 0xCC,
        DF_INS_CREATE_LINEAR_RECORD_FILE = 0xC1,
        DF_INS_CREATE_CYCLIC_RECORD_FILE = 0xC0,
        DF_INS_DELETE_FILE = 0xDF,


        DF_INS_READ_DATA = 0xBD,
        DF_INS_WRITE_DATA = 0x3D,
        DF_INS_GET_VALUE = 0x6C,
        DF_INS_CREDIT = 0x0C,
        DF_INS_DEBIT = 0xDC,
        DF_INS_LIMITED_CREDIT = 0x1C,
        DF_INS_WRITE_RECORD = 0x3B,
        DF_INS_READ_RECORDS = 0xBB,
        DF_INS_CLEAR_RECORD_FILE = 0xEB,
        DF_COMMIT_TRANSACTION = 0xC7,
        DF_INS_ABORT_TRANSACTION = 0xA7,

        /// <summary>
        /// data did not fit into a frame, another frame will follow
        /// </summary>
        DF_INS_ADDITIONAL_FRAME = 0xAF,
        #endregion

        #region DESFire EV1 instructions
        DFEV1_INS_AUTHENTICATE_ISO = 0x1A,
        DFEV1_INS_AUTHENTICATE_AES = 0xAA,
        DFEV1_INS_FREE_MEM = 0x6E,
        DFEV1_INS_GET_DF_NAMES = 0x6D,
        DFEV1_INS_GET_CARD_UID = 0x51,
        DFEV1_INS_GET_ISO_FILE_IDS = 0x61,
        DFEV1_INS_SET_CONFIGURATION = 0x5C,
        #endregion

        #region ISO7816 instructions
        ISO7816_INS_EXTERNAL_AUTHENTICATE = 0x82,
        ISO7816_INS_INTERNAL_AUTHENTICATE = 0x88,
        ISO7816_INS_APPEND_RECORD = 0xE2,
        ISO7816_INS_GET_CHALLENGE = 0x84,
        ISO7816_INS_READ_RECORDS = 0xB2,
        ISO7816_INS_SELECT_FILE = 0xA4,
        ISO7816_INS_READ_BINARY = 0xB0,
        ISO7816_INS_UPDATE_BINARY = 0xD6
        #endregion
    }
    public bool Authenticate(byte[] key, byte[] initVector)
    {

        var tdes = new TripleDESCryptoServiceProvider()
        {
            Mode = CipherMode.CBC,
            Padding = PaddingMode.None,
            BlockSize = 64,
            IV = initVector
        };

        var decryptor = tdes.CreateWeakDecryptor(key, initVector);


        byte[] piccApplication = new byte[3] { 0x00, 0x00, 0x00 };
        byte[] response = null;
        // Select PICC Application 0x00 0x00 0x00
        DESFireStatus status = DataExchange(Instructions.DF_INS_SELECT_APPLICATION, 0, 0, piccApplication, out response);

        status = DataExchange(Instructions.DF_INS_GET_KEY_SETTINGS, 0, 0, null, out response);
        //-> BufOut Byte Nr 0 : 0F => All bits in lower nibble are set, meaning configuration can be changed, CreateApp/ GetAppIds / GetKeySettings can be performed without masterkey, and masterkey is changeable
        //-> BufOut Byte Nr 1 : 01 => Only 1 key can exist for this application(the PICC application)

        byte[] keyNumber = new byte[1];
        keyNumber[0] = 0;
        status = DataExchange(Instructions.DF_INS_GET_KEY_VERSION, 0, 0, keyNumber, out response);      // Get Key Version for Key 0            

        // Get RandB_enc from Card
        status = DataExchange(Instructions.DF_INS_AUTHENTICATE_LEGACY, 0, 0, keyNumber, out response);  // Authenticate for Key 0


        byte[] RndB_enc = new byte[8];
        Array.Copy(response, RndB_enc, response.Length);
        ShowBytes(RndB_enc, "RndB_enc");

        // Decrypt RndB_enc to RndB
        var RndB = decryptor.TransformFinalBlock(RndB_enc, 0, RndB_enc.Length);
        ShowBytes(RndB, "RndB");

        // Rotate RndB 1 Byte to the left
        var RndB_rot = RotateLeft(RndB);
        ShowBytes(RndB_rot, "RndB_rot");

        // RndA should be random bytes, instead of hardcoded bytes are here shown
        var RndA = StringToByteArray("84 9B 36 C5 F8 BF 4A 09");
        ShowBytes(RndA, "RndA");

        // Create a DECRYPTED Version of RndA  (Because the Card will ENCRYPT it)
        var RndA_dec = decryptor.TransformFinalBlock(RndA, 0, RndA.Length);
        ShowBytes(RndA_dec, "RndA_dec");

        // XOR RndA_dec with RndB_rot
        var aXb = XorBlocks(RndA_dec, RndB_rot);
        ShowBytes(aXb, "(RndA_dec) xor (RndB_rot)");

        // DECRYPT the XORed value
        var aXb_dec = decryptor.TransformFinalBlock(aXb, 0, aXb.Length);
        ShowBytes(aXb_dec, "Decrypt above result");

        // Concatenate RndA_dec with Xored_dec
        var dataToSend = RndA_dec.Concat(aXb_dec).ToArray();
        ShowBytes(dataToSend, "Data to send");

        // Send the result to the Card and get the response from the card
        status = DataExchange(Instructions.DF_INS_ADDITIONAL_FRAME, 0, 0, dataToSend, out response);

        // Card will return RndA ENCRYPTED
        byte[] RndA_FC_enc = new byte[response.Length];
        Array.Copy(response, RndA_FC_enc, response.Length);

        // DECRYPT the received value from the card
        byte[] RndA_FC_dec = decryptor.TransformFinalBlock(RndA_FC_enc, 0, RndA_FC_enc.Length);

        // Rotate the decrypted value from the card 1 byte right
        byte[] RndA_FC_dec_rot = RotateRight(RndA_FC_dec);

        // Check that the received, decrypted and right-shifted value from the card is egqual to the originial RndA
        if (!IsEqualTo(RndA, RndA_FC_dec_rot))
        {
            throw new Exception($"Error Authenticating CARD. RndA is not equal to RndA_FC_dec_rot");
        }

        SessionKey = new byte[16];
        Array.Copy(RndA, 0, SessionKey, 0, 4);
        Array.Copy(RndB, 0, SessionKey, 4, 4);
        Array.Copy(RndA, 4, SessionKey, 8, 4);
        Array.Copy(RndB, 4, SessionKey, 12, 4);

        tdes.Clear();
        return true;
    }

    private DESFireStatus DataExchange(DesFire.Instructions instruction, byte P1, byte P2, byte[] data, out byte[] response)
    {
        response = null;

        byte[] BufOut = new Byte[512];

        // Create the APDU Command which is sent the CARD
        byte[] apdu = new byte[5];
        apdu[0] = 0x90;                 // APDU-Command Class
        apdu[1] = (byte)instruction;    // APDU-Command Instruction
        apdu[2] = P1;                   // APDU-Command Parameter 1
        apdu[3] = P2;                   // APDU-Command Parameter 2
        if (data != null && data.Length > 0)
        {
            apdu[4] = (byte)data.Length;    // APDU-Command Parameter 3 (Length)
            Array.Resize(ref apdu, 6 + data.Length);    // Increase APDU Command byte array to fit the bytes from data
            Array.Copy(data, 0, apdu, 5, data.Length);  // Copy data bytes into APDU-Command
        }
        else
        {
            apdu[4] = 0x00;             // APDU-Command Parameter 3 (Lenght)
        }
        
        int bufOutLen = BufOut.Length;      // In-Out Parameter telling the CARD the maximum number of bytes allowed for BufOut, and afterwand reading the total number of bytes the card has returned
        // Send APDU Command to Card
        NativeMethods.ErrorCode erno = NativeMethods.card_PipeX(ref this._card, apdu, apdu.Length, BufOut, ref bufOutLen);
        if (erno != NativeMethods.ErrorCode._NoError)
        {
            throw new Exception($"Error on DataExchange for instruction '{instruction}'. ErrorNumber: '{erno}");
        }

        // Did the card return at least two bytes
        if (bufOutLen >= 2)
        {
            // SW1 should be 0x91
            byte SW1 = BufOut[bufOutLen - 2];

            // SW2 gives the status-message of the last call to the CARD
            DESFireStatus SW2 = (DesFire.DESFireStatus)BufOut[bufOutLen - 1];

            if (bufOutLen > 2)
            {
                // Did the CARD return a value, if so, copy the response from the CARD to the response-output-parameter of this function
                response = new byte[bufOutLen - 2];
                Array.Copy(BufOut, response, bufOutLen - 2);
            }
            return SW2;
        }
        else
        {
            throw new Exception("Invalid response from CARD");
        }
    }

    /// <summary>
    /// Display array of bytes on console
    /// </summary>
    /// <param name="resultArray">bytes to show</param>
    /// <param name="message">optional message to display</param>
    private static void ShowBytes(byte[] resultArray, string message = "")
    {
        Console.Write((message + ": ").PadLeft(20, ' '));
        for (int i = 0; i < resultArray.Length; i++)
            Console.Write("{0:X2} ", resultArray[i]);
        Console.WriteLine();
    }

    /// <summary>
    /// Rotate byte array left by one
    /// </summary>
    /// <param name="source">original byte array</param>
    /// <returns>rotated byte array</returns>
    static byte[] RotateLeft(byte[] source)
    {
        return source.Skip(1).Concat(source.Take(1)).ToArray();
    }

    /// <summary>
    /// Rotate byte array right by one
    /// </summary>
    /// <param name="source">original byte array</param>
    /// <returns>rotated byte array</returns>
    static byte[] RotateRight(byte[] source)
    {
        return source.Skip(source.Length - 1).Concat(source.Take(source.Length - 1)).ToArray();
    }

    /// <summary>
    /// XOR two byte arrays
    /// </summary>
    /// <param name="b1">first byte array</param>
    /// <param name="b2">second byte array</param>
    /// <returns>xor-ed array</returns>
    private static byte[] XorBlocks(byte[] b1, byte[] b2)
    {
        byte[] result = new byte[8];
        for (int i = 0; i <= 7; i++)
        {
            result[i] = (byte)(b1[i] ^ b2[i]);
        }
        return result;
    }

    public static byte[] StringToByteArray(string hex)
    {
        hex = hex.Replace(" ", string.Empty);
        return Enumerable.Range(0, hex.Length)
                         .Where(x => x % 2 == 0)
                         .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
                         .ToArray();
    }

    [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
    static extern int memcmp(byte[] b1, byte[] b2, long count);

    public static bool IsEqualTo(byte[] b1, byte[] b2)
    {
        // Validate buffers are the same length.
        // This also ensures that the count does not exceed the length of either buffer.  
        return b1.Length == b2.Length && memcmp(b1, b2, b1.Length) == 0;
    }

}
}

and

using System.Reflection;
using System.Security.Cryptography;

namespace DesFireStackOverflow
{
    public static class ExtensionFunctions
    {
    #region TrippleDESCryptoExtensions
        public static ICryptoTransform CreateWeakEncryptor(this TripleDESCryptoServiceProvider cryptoProvider, byte[] key, byte[] iv)
        {
            MethodInfo mi = cryptoProvider.GetType().GetMethod("_NewEncryptor", BindingFlags.NonPublic | BindingFlags.Instance);
            object[] Par = { key, cryptoProvider.Mode, iv, cryptoProvider.FeedbackSize, 0 };
            ICryptoTransform trans = mi.Invoke(cryptoProvider, Par) as ICryptoTransform;
            return trans;
        }
        public static ICryptoTransform CreateWeakEncryptor(this TripleDESCryptoServiceProvider cryptoProvider)
        {
            return CreateWeakEncryptor(cryptoProvider, cryptoProvider.Key, cryptoProvider.IV);
        }
        public static ICryptoTransform CreateWeakDecryptor(this TripleDESCryptoServiceProvider cryptoProvider, byte[] key, byte[] iv)
        {
            return CreateWeakEncryptor(cryptoProvider, key, iv);
        }
        public static ICryptoTransform CreateWeakDecryptor(this TripleDESCryptoServiceProvider cryptoProvider)
        {
            return CreateWeakDecryptor(cryptoProvider, cryptoProvider.Key, cryptoProvider.IV);
        }
        #endregion
    }
}
Markus1980Wien
  • 471
  • 1
  • 5
  • 15