From 307bd609251f3e8b95f5f3557a2800baaa3542c4 Mon Sep 17 00:00:00 2001
From: TheJoKlLa <thejoklla@gmail.com>
Date: Thu, 8 Oct 2020 17:23:48 +0200
Subject: [PATCH] Added: ChangeKey AES Same Key

---
 NFC/NXP MIFARE DESFire/MIFARE_DESFire_V2.cs | 198 +++++++++++++++++++-
 NFC_Test/MIFARE_DESFire_V2_Test.cs          |  83 ++++++++
 NFC_Test/OTA.cs                             |  44 ++++-
 3 files changed, 322 insertions(+), 3 deletions(-)

diff --git a/NFC/NXP MIFARE DESFire/MIFARE_DESFire_V2.cs b/NFC/NXP MIFARE DESFire/MIFARE_DESFire_V2.cs
index 6222665..df5fbe2 100644
--- a/NFC/NXP MIFARE DESFire/MIFARE_DESFire_V2.cs	
+++ b/NFC/NXP MIFARE DESFire/MIFARE_DESFire_V2.cs	
@@ -299,7 +299,7 @@ namespace NFC.Mifare_DESFire
         /// Generates SessionKey for DES Authentification
         /// </summary>
         /// <returns>16Byte SessionKey</returns>
-        private byte[] GenerateSesionKey_DES(byte[] rndA, byte[] rndB)
+        public byte[] GenerateSesionKey_DES(byte[] rndA, byte[] rndB)
         {
             byte[] sesssionkey = new byte[8];
 
@@ -319,6 +319,37 @@ namespace NFC.Mifare_DESFire
             return Concatenate(sesssionkey, sesssionkey);
         }
 
+        /// <summary>
+        /// Generates SessionKey for AES Authentification
+        /// </summary>
+        /// <returns>16Byte SessionKey</returns>
+        public byte[] GenerateSesionKey_AES(byte[] rndA, byte[] rndB)
+        {
+            byte[] sesssionkey = new byte[16];
+
+            for (int i = 0; i < sesssionkey.Length; i++)
+            {
+                if (i < 4)
+                {
+                    sesssionkey[i] = rndA[i];
+                }
+                else if(i >= 4 && i < 8)
+                {
+                    sesssionkey[i] = rndB[i - 4];
+                }
+                else if (i >= 8 && i < 12)
+                {
+                    sesssionkey[i] = rndA[i + 4];
+                }
+                else
+                {
+                    sesssionkey[i] = rndB[i];
+                }
+            }
+
+            return sesssionkey;
+        }
+
         /// <summary>
         /// Set KeyVersion in DES Key
         /// KeyVersion is stored in LSB of the first 8 Bytes of the DES Key
@@ -347,6 +378,30 @@ namespace NFC.Mifare_DESFire
 
             return new_key;
         }
+
+        /// <summary>
+        /// Expand Array to Block Size, fill with 0x00
+        /// </summary>
+        /// <param name="data"></param>
+        public byte[] ExpandToBlockSize(byte[] data, uint bocksize)
+        {
+            int diff = data.Length % (int)bocksize;
+            if (diff == 0)
+            {
+                return data;
+            }
+
+            byte[] expand = new byte[data.Length + bocksize - diff];
+
+            data.CopyTo(expand, 0);
+
+            for (int i = expand.Length - 1; i > data.Length - 1; i--)
+            {
+                expand[i] = 0x00;
+            }
+
+            return expand;
+        }
         #endregion
 
         #region DESFire Commands
@@ -530,6 +585,147 @@ namespace NFC.Mifare_DESFire
 
             _Log.Debug("End SelectApplication");
         }
+
+        /// <summary>
+        /// Authenticate to PICC, with ISO Authenticate
+        /// </summary>
+        /// <param name="key_id">0x01 - 0x0D</param>
+        /// <param name="key"></param>
+        /// <param name="rndA">!!! WARNING For Testing only !!!</param>
+        /// <exception cref="AuthenticationDelayException">Retry after short Time</exception>
+        public void AuthenticateISO_AES(byte key_id, byte[] key, byte[] rndA = null)
+        {
+            _Log.Debug("Start AuthenticateISO_AES");
+
+            // Sepearte InitialisationVector for Authentication
+            byte[] iv = new byte[16];
+
+            APDUCommand cmd_challange_request = new APDUCommand(IsoCase.Case4Short)
+            {
+                CLA = 0x90,
+                INS = 0xAA,
+                Data = new byte[]
+                {
+                    key_id
+                }
+            };
+            _Log.DebugFormat("APDU_CMD(cmd_challange_request): {0}", ConvertToHexString(cmd_challange_request.ToArray()));
+
+            APDUResponse response = _Card.Transmit(cmd_challange_request);
+            _Log.DebugFormat("APDU_RES(cmd_challange_request): {0}", ConvertToHexString(response.ToArray()));
+
+            CheckAPDUResponse(response);
+
+            byte[] rndB_enc = response.Body;
+            _Log.DebugFormat("rndB_enc: {0}", ConvertToHexString(rndB_enc));
+
+            AES aes = new AES();
+            byte[] rndB = aes.Decrypt(rndB_enc, key, GenerateEmptyKey(16));
+            _Log.DebugFormat("rndB: {0}", ConvertToHexString(rndB));
+
+            rndB.CopyTo(iv, 0);
+
+            byte[] rndB_rl = RotateLeft(rndB);
+            _Log.DebugFormat("rndB_enc: {0}", ConvertToHexString(rndB_rl));
+
+            if (rndA == null)
+            {
+                Random rnd = new Random();
+                rndA = new byte[16];
+                rnd.NextBytes(rndA);
+            }
+            _Log.DebugFormat("rndA: {0}", ConvertToHexString(rndA));
+
+            byte[] rndAB = Concatenate(rndA, rndB_rl);
+            _Log.DebugFormat("rndAB: {0}", ConvertToHexString(rndAB));
+
+            byte[] rndAB_enc = aes.Encrypt(rndAB, key, rndB_enc);
+            _Log.DebugFormat("rndA_rndB_enc: {0}", ConvertToHexString(rndAB_enc));
+            iv = ExtractLastBlock(rndAB_enc, 16);
+
+            APDUCommand cmd_challange_response = new APDUCommand(IsoCase.Case4Short)
+            {
+                CLA = 0x90,
+                INS = 0xAF,
+                Data = rndAB_enc
+            };
+            _Log.DebugFormat("APDU_CMD(cmd_challange_response): {0}", ConvertToHexString(cmd_challange_response.ToArray()));
+
+            response = _Card.Transmit(cmd_challange_response);
+            _Log.DebugFormat("APDU_RES(cmd_challange_response): {0}", ConvertToHexString(cmd_challange_response.ToArray()));
+
+            CheckAPDUResponse(response);
+
+            byte[] encryptedRndAFromCard = response.Body;
+            _Log.DebugFormat("encryptedRndAFromCard: {0}", ConvertToHexString(encryptedRndAFromCard));
+
+            byte[] rotatedRndAFromCard = aes.Decrypt(encryptedRndAFromCard, key, iv);
+            _Log.DebugFormat("rotatedRndAFromCard: {0}", ConvertToHexString(rotatedRndAFromCard));
+
+            byte[] rndAFromCard = RotateRight(rotatedRndAFromCard);
+            _Log.DebugFormat("rndAFromCard: {0}", ConvertToHexString(rndAFromCard));
+
+            if (!rndA.SequenceEqual(rndAFromCard))
+            {
+                throw new Exception("Authentication failed, PICC Challenge is not corret");
+            }
+
+            _SessionKey = GenerateSesionKey_AES(rndA, rndB);
+            _Log.DebugFormat("_SessionKey: {0}", ConvertToHexString(_SessionKey));
+
+            _IV = GenerateEmptyKey(16);
+            _Log.DebugFormat("_IV: {0}", ConvertToHexString(_IV));
+
+            _Log.Debug("End AuthenticateISO_DES");
+        }
+
+        public void ChangeKey_AES(byte key_id, byte[] new_key, byte key_version)
+        {
+            _Log.Debug("Start ChangeKey_AES");
+
+            byte[] header = new byte[]
+            {
+                0xC4, key_id
+            };
+            _Log.DebugFormat("header: {0}", ConvertToHexString(header));
+
+            // AES Key Version is Append to Key
+            byte[] key_and_version = Concatenate(new_key, new byte[] { key_version });
+            byte[] command = Concatenate(header, key_and_version);
+            _Log.DebugFormat("command: {0}", ConvertToHexString(command));
+
+            CRC32 crc32 = new CRC32();
+            byte[] crc = crc32.Calculate(command);
+            _Log.DebugFormat("crc: {0}", ConvertToHexString(crc));
+
+            byte[] cryptogram = Concatenate(key_and_version, crc);
+            _Log.DebugFormat("cryptogram: {0}", ConvertToHexString(cryptogram));
+
+            byte[] cryptogram_block = ExpandToBlockSize(cryptogram, 16);
+            _Log.DebugFormat("cryptogram_block: {0}", ConvertToHexString(cryptogram_block));
+
+            AES aes = new AES();
+            byte[] cryptogram_enc = aes.Encrypt(cryptogram_block, _SessionKey, _IV);
+            _Log.DebugFormat("cryptogram_enc: {0}", ConvertToHexString(cryptogram_enc));
+
+            byte[] data = Concatenate(new byte[] { key_id }, cryptogram_enc);
+            _Log.DebugFormat("data: {0}", ConvertToHexString(data));
+
+            APDUCommand cmd_ChangeKey = new APDUCommand(IsoCase.Case4Short)
+            {
+                CLA = 0x90,
+                INS = 0xC4,
+                Data = data
+            };
+            _Log.DebugFormat("APDU_CMD(cmd_ChangeKey): {0}", ConvertToHexString(cmd_ChangeKey.ToArray()));
+
+            APDUResponse response = _Card.Transmit(cmd_ChangeKey);
+            _Log.DebugFormat("APDU_RES(cmd_ChangeKey): {0}", ConvertToHexString(response.ToArray()));
+
+            CheckAPDUResponse(response);
+
+            _Log.Debug("End ChangeKey_AES");
+        }
         #endregion
 
         #region Configuration Generator
diff --git a/NFC_Test/MIFARE_DESFire_V2_Test.cs b/NFC_Test/MIFARE_DESFire_V2_Test.cs
index 8ddbe05..2b7a7c2 100644
--- a/NFC_Test/MIFARE_DESFire_V2_Test.cs
+++ b/NFC_Test/MIFARE_DESFire_V2_Test.cs
@@ -3,8 +3,11 @@ using NFC.ISO7816_4;
 using NFC.Mifare_DESFire;
 using NFC.NXP_MIFARE_DESFire.Exceptions;
 using NSubstitute;
+using NSubstitute.Core;
 using NUnit.Framework;
+using PCSC.Iso7816;
 using System;
+using System.Collections.Generic;
 using System.Net;
 
 namespace NFC_Test
@@ -508,6 +511,24 @@ namespace NFC_Test
                 byte[] lastblock = desfire.XOR(null, null);
             });
         }
+
+        [Test]
+        public void GenerateSessionKey_AES()
+        {
+            MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(null);
+
+            byte[] rndA = desfire.ConvertFromHexString("bc14dfde20074617e45a8822f06fdd91");
+            Console.WriteLine(desfire.ConvertToHexString(rndA));
+            byte[] rndB = desfire.ConvertFromHexString("482ddc54426e6dee560413b8d95471f5");
+            Console.WriteLine(desfire.ConvertToHexString(rndB));
+
+            byte[] expected_sessionkey = desfire.ConvertFromHexString("bc14dfde482ddc54f06fdd91d95471f5");
+            Console.WriteLine(desfire.ConvertToHexString(expected_sessionkey));
+
+            byte[] sessionkey = desfire.GenerateSesionKey_AES(rndA, rndB);
+            Console.WriteLine(desfire.ConvertToHexString(sessionkey));
+            Assert.AreEqual(expected_sessionkey, sessionkey);
+        }
         #endregion
 
         #region DESFire Commands
@@ -546,6 +567,68 @@ namespace NFC_Test
             Assert.AreEqual(expected_sessionkey, desfire._SessionKey);
             Assert.AreEqual(expected_iv, desfire._IV);
         }
+
+        [Test]
+        public void AuthenticateISO_AES()
+        {
+            ICard card = Substitute.For<ICard>();
+
+            MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(card);
+
+            APDUResponse response_challenge_request = new APDUResponse()
+            {
+                SW1 = 0x91,
+                SW2 = 0xAF,
+                Body = desfire.ConvertFromHexString("43a28e28c653df83cd85039714bccb51")
+            };
+
+            APDUResponse response_challenge_response = new APDUResponse()
+            {
+                SW1 = 0x91,
+                SW2 = 0x00,
+                Body = desfire.ConvertFromHexString("d8f70a0f9a43f522f775a56f5688592f")
+            };
+
+            byte[] rndA = desfire.ConvertFromHexString("8a8b3c15e576ae3a21c2b18e6aead1f1");
+            byte[] key = desfire.ConvertFromHexString("00000000000000000000000000000000");
+
+            card.Transmit(Arg.Is<APDUCommand>(x => x.INS == 0xAA)).Returns(response_challenge_request);
+            card.Transmit(Arg.Is<APDUCommand>(x => x.INS == 0xAF)).Returns(response_challenge_response);
+
+            desfire.AuthenticateISO_AES(0x00, key, rndA);
+
+            byte[] expected_sessionkey = desfire.ConvertFromHexString("8a8b3c15c71d0cf46aead1f148f27703");
+            byte[] expected_iv = desfire.GenerateEmptyKey(16);
+
+            Assert.AreEqual(expected_sessionkey, desfire._SessionKey);
+            Assert.AreEqual(expected_iv, desfire._IV);
+        }
+
+        [Test]
+        public void ChangeKey_AES()
+        {
+            ICard card = Substitute.For<ICard>();
+
+            MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(card);
+
+            APDUResponse response = new APDUResponse()
+            {
+                SW1 = 0x91,
+                SW2 = 0x00
+            };
+
+            byte[] new_key = desfire.ConvertFromHexString("45eeb8338ae8f49a032e85bb11143530");
+
+            byte[] sessionkey = desfire.ConvertFromHexString("2f96515262e1beb0129de2df3e97feb3");
+            byte[] iv = desfire.ConvertFromHexString("00000000000000000000000000000000");
+
+            desfire._SessionKey = sessionkey;
+            desfire._IV = iv;
+
+            card.Transmit(null).ReturnsForAnyArgs(response);
+
+            desfire.ChangeKey_AES(0x00, new_key, 0x10);
+        }
         #endregion
     }
 }
diff --git a/NFC_Test/OTA.cs b/NFC_Test/OTA.cs
index 8ff2752..b5eb63f 100644
--- a/NFC_Test/OTA.cs
+++ b/NFC_Test/OTA.cs
@@ -39,9 +39,49 @@ namespace NFC_Test
 
                 byte keySetting1 = desfire.GenerateKeySetting1(ChangeApplicationKey.MASTERKEY, ChangeMasterKeySettings.WITHMASTERKEY, CreateDeleteFile.NOKEY, FileDirectoryAccess.NOKEY, ChangeMasterKey.CHANGEABLE);
                 byte keySetting2 = desfire.GenerateKeySetting2(CryptoOperationsType.AES, FileIdentifies.NOTUSED, 0x03);
-                desfire.CreateApplication(0xAAFFEE, keySetting1, keySetting2);
+                desfire.CreateApplication(0xC0FFEE, keySetting1, keySetting2);
 
-                desfire.SelectApplication(0xAAFFEE);
+                desfire.SelectApplication(0xC0FFEE);
+                desfire.AuthenticateISO_AES(0x00, desfire.GenerateEmptyKey(16));
+
+                byte[] new_key = desfire.ConvertFromHexString("45eeb8338ae8f49a032e85bb11143530");
+
+                desfire.ChangeKey_AES(0x00, new_key, 0x10);
+
+                transmit_successfully = true;
+
+                card.Disconnect();
+            };
+
+            reader.CardDiscovered += handler;
+            reader.Start();
+
+            Assert.AreEqual(true, transmit_successfully);
+
+            reader.Stop();
+            reader.CardDiscovered -= handler;
+        }
+
+        [Test]
+        public void ChangeKey()
+        {
+            IHardware hardware = new Hardware();
+            IReader reader = hardware.OpenReader(_ReaderID);
+
+            bool transmit_successfully = false;
+
+            ReaderEventHandler handler = (sender, card) =>
+            {
+                card.Connect();
+
+                MIFARE_DESFire_V2 desfire = new MIFARE_DESFire_V2(card);
+
+                desfire.SelectApplication(0xC0FFEE);
+                desfire.AuthenticateISO_AES(0x00, desfire.GenerateEmptyKey(16));
+
+                byte[] new_key = desfire.ConvertFromHexString("45eeb8338ae8f49a032e85bb11143530");
+
+                desfire.ChangeKey_AES(0x00, new_key, 0x10);
 
                 transmit_successfully = true;