我在以下页面上找到了一个关于如何进行身份验证的工作示例:https ://blog.chaucery.com/2017/02/desfire-authentication-in-c.html
页面https://ridrix.wordpress.com/2009/09/19/mifare-desfire-communication-example/也非常有帮助。
不要忘记查看此 codeproject 文章: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
}
}