如何在desfire ev1中进行本机身份验证

时间:2013-01-14 13:28:46

标签: c# mifare

我想在纯模式下使用desfire卡进行身份验证。 以下是我目前的步骤:

  • 我选择主应用程序(AID = 0×00 0×00 0×00)
  • 我从卡片
  • 收到回复(“挑战”,randB)
  • 然后,在第二步中,我生成一个响应(randA);我将8字节rand A和8字节解密并旋转了rand B
  • 连接起来
  • 我加密它,把它发回卡片。

我收到91 ae个答案,我不知道为什么。这是消息序列:

  • 到卡:90 0A 00 00 01 00 00
  • 来自卡5B 57 69 C7 CE 4B 16 7B 91 AF
  • 加密RandB = 5B 57 69 C7 CE 4B 16 7B
  • 破译RandB'= 17 8D 23 57 10 C9 32 D5
  • 一个字节lshitf RandB'= 8D 23 57 10 C9 32 D5 17
  • 生成的兰特A = 43 9D 17 8E 9A 5F BA 70
  • 连接兰德A与兰德B''= 43 9D 17 8E 9A 5F BA 70 8D 23 57 10 C9 32 D5 17
  • 加密Rand A + Rand B''= 9E ED DC 4F BC E7 BE BD 09 02 CF 99 F7 40 34 7B
  • 到卡:90 AF 00 00 10 43 9D 17 8E 9A 5F BA 70 8D 23 57 10 C9 32 D5 17 00
  • 来自卡:91 AE

拜托,如果你看错了,请指出吗?我可能有哪些问题?

4 个答案:

答案 0 :(得分:2)

我知道回复有点迟了,但无论如何都要回答 - 我自己昨天才能获得正确的身份验证。 91 ae表示验证错误。这意味着你在连接和加密后发送给RandA的RandB'并不像PICC(Ev1)那样预期。问题可能在于加密。您可以通过EV1数据表中的示例交叉检查您的逻辑编码吗?

希望它有所帮助(如果您无法自己解决问题)

答案 1 :(得分:1)

主密钥和其他应用程序身份验证使用相同的逻辑。 Here是我在Android中DesFire身份验证主题中的问题和答案。希望它有所帮助。

你的问题必须在en / deciphering中。您应该使用3DES或AES进行CBC no-padding

答案 2 :(得分:0)

根据我的经验,大部分时间都是以错误方式完成的加密。您使用正确的模式加密响应吗? (顺便说一下,您使用的是DES,TDES还是AES?)。如果您使用DES,您是否在反向模式下使用CBC? IIRC,加密您需要使用“反向”算法的响应(您通常用于chipering的算法)。另外,检查密钥是否需要奇偶校验。

答案 3 :(得分:0)

我在以下页面找到了一个关于如何进行身份验证的工作示例:https://blog.chaucery.com/2017/02/desfire-authentication-in-c.html

还有页面 https://ridrix.wordpress.com/2009/09/19/mifare-desfire-communication-example/ 真的很有帮助。

不要忘记查看此代码项目文章:https://www.codeproject.com/Articles/1096861/DIY-electronic-RFID-Door-Lock-with-Battery-Backup 和此处给出的示例:https://hack.cert.pl/files/desfire-9f122c71e0057d4f747d2ee295b0f5f6eef8ac32.html

在这些博文的帮助下,我能够编写以下代码

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;
    }

}
}

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
    }
}