A good overview of DESFire communications is provided on Ridrix's Blog. Although Ridrix does not detail the authentication process, a comment does give sample expected values and enough of a description to work with. I have copied those values below, and provided a program in C# which will produce those values.
The Stack Overflow question DES Send and Receive Modes for DESFire Authentication contains code to perform authentication on Android, but the cryptographic libraries for Android and .NET are different enough that conversion to C# is not straightforward.
With regards my sample code, note that the key, RndA and Encrypted_RndB are hard-coded. For real usage you will use your own key, produce your own random bytes for RndA, and receive a different Encrypted_RndB from the DESFire card.
| Key | 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF |
| RndA | B7 1F AA B7 2D 3C 4F 50 |
| Decrypted_RndA | 31 C3 51 43 66 3E 7A 00 |
| Encrypted_RndB | 8A 8E E9 C5 99 B6 4A 5C |
| RndB | 52 63 DA C1 FF 2C EA 14 |
| RndB_rotated | 63 DA C1 FF 2C EA 14 52 |
| (Decrypted_RndA) xor (RndB_rotated) | 52 19 90 BC 4A D4 6E 52 |
| Decrypt above result | D7 08 FE E9 F2 3B 66 9E |
| Decrypted_RndA + above | 31 C3 51 43 66 3E 7A 00 D7 08 FE E9 F2 3B 66 9E |
using System;
using System.Linq;
using System.Security.Cryptography;
namespace DESFire.Authentication
{
class Program
{
static void Main(string[] args)
{
var key = StringToByte("00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF");
var initVector = StringToByte("00 00 00 00 00 00 00 00");
var tdes = new TripleDESCryptoServiceProvider()
{
Key = key,
Mode = CipherMode.CBC,
Padding = PaddingMode.None,
BlockSize = 64,
IV = initVector
};
var decryptor = tdes.CreateDecryptor();
// Normally you'd create your own random string
//byte[] RndA = new byte[8];
//var r = new Random();
//r.NextBytes(RndA);
var RndA = StringToByte("B7 1F AA B7 2D 3C 4F 50");
ShowBytes(RndA, "RndA");
var RndA_dec = decryptor.TransformFinalBlock(RndA, 0, RndA.Length);
ShowBytes(RndA_dec, "RndA_dec");
// Normally you would use the data returned from the card
var RndB_enc = StringToByte("8A 8E E9 C5 99 B6 4A 5C");
ShowBytes(RndB_enc, "RndB_enc");
var RndB = decryptor.TransformFinalBlock(RndB_enc, 0, RndB_enc.Length);
ShowBytes(RndB, "RndB");
var RndB_rot = RotateLeft(RndB);
ShowBytes(RndB_rot, "RndB_rot");
var aXb = XorBlocks(RndA_dec, RndB_rot);
ShowBytes(aXb, "(RndA_dec) xor (RndB_rot)");
var aXb_dec = decryptor.TransformFinalBlock(aXb, 0, aXb.Length);
ShowBytes(aXb_dec, "Decrypt above result");
var dataToSend = RndA_dec.Concat(aXb_dec).ToArray();
ShowBytes(dataToSend, "Data to send");
tdes.Clear();
Console.ReadKey();
}
/// <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;
}
/// <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>
/// Convert string of hexadecimal text into array of bytes
/// </summary>
/// <param name="hexWithSpaces">string of hex bytes with optional spaces</param>
/// <returns>array of bytes</returns>
public static byte[] StringToByte(string hexWithSpaces)
{
var hex = hexWithSpaces.Replace(" ", "");
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
/// <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();
}
}
}
Note that if you don't have exclusive access to the card you may need to wrap your authentication in a transaction.