Nav links

Saturday, 4 February 2017

DESFire authentication in C#

Previously I've posted how to communicate with a DESFire card in C#. To do anything useful with the card requires authentication, a process which is a trifle fiddly, but straightforward when you see the code.

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
RndAB7 1F AA B7 2D 3C 4F 50
Decrypted_RndA31 C3 51 43 66 3E 7A 00
Encrypted_RndB8A 8E E9 C5 99 B6 4A 5C
RndB52 63 DA C1 FF 2C EA 14
RndB_rotated63 DA C1 FF 2C EA 14 52
(Decrypted_RndA) xor (RndB_rotated)52 19 90 BC 4A D4 6E 52
Decrypt above resultD7 08 FE E9 F2 3B 66 9E
Decrypted_RndA + above31 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.