using NFC.Crypto; using NFC.ISO7816_4; using NFC.Mifare_DESFire.Enums; using PCSC.Iso7816; using System; using System.Collections.Generic; using System.Linq; namespace NFC.Mifare_DESFire { public class MIFARE_DESFire { // Docs https://hackmd.io/qATu8uYdRnOC40aFrB9afg #region Contructors /// /// Construct MIFRARE_DESFire Object with ICard Interface /// /// Implementation of ICard, only transmit is used public MIFARE_DESFire(ICard card) { _Card = card; } #endregion #region Properties /// /// ICard Implementation used to transmit APDUCommands and recive APDUResponses /// private ICard _Card; public byte[] _SessionKey; public byte[] _IV; #endregion #region Methods /// /// Generate Byte Array filled with 0 /// /// Size of Array public byte[] GenerateDefaultKey(uint size) { byte[] key = new byte[size]; for (int i = 0; i < size; i++) { key[i] = 0; } return key; } /// /// Converts byte[] to string with HEX Code /// No 0x is created /// /// Data public string ConvertToHexString(byte[] data) { return BitConverter.ToString(data).Replace("-", " "); } public void CheckAPDUResponse(APDUResponse response) { } public bool CheckKey(byte[] key) { try { GetKeyTypeDES(key); return true; } catch { return false; } } /// /// Key Types used for DESFire Cards /// public enum KeyType { /// /// DES Key - 8 Byte - 64 Bit /// DES, /// /// Triple DES Key with two DES Keys - 16 Byte - 128 Bit /// TDES_2K, /// /// Triple DES Key with three DES Keys - 24 Byte - 192 Bit /// TDES_3K, /// /// AES Key - 16 Byte - 128 Bit /// AES } /// /// Check the Key Length to get Type of DES/3DES Key /// public KeyType GetKeyTypeDES(byte[] key) { switch(key.Length) { case 8: return KeyType.DES; case 16: return KeyType.TDES_2K; case 24: return KeyType.TDES_3K; default: throw new ArgumentException(string.Format("No valid DES/3DES Key Size({0})", key.Length)); } } #region Methods for Crypto Operation /// /// Return a copy of the last Block of data /// /// Data compatible to blocksize /// Size of Block, default is 8 public byte[] GetLastBlock(byte[] data, uint blocksize = 8) { if(data.Length % blocksize != 0) { throw new ArgumentException(string.Format("Data is not compatible with blocksize(data(length):{0}, blocksize:{1}", data.Length, blocksize)); } byte[] block = new byte[blocksize]; for (int i = 0; i < blocksize; i++) { block[i] = data[data.Length - blocksize + i]; } return block; } /// /// Rotates Array to the left /// /// Data /// Copy of data public byte[] rotateLeft(byte[] data) { byte[] rotate = new byte[data.Length]; data.CopyTo(rotate, 0); byte tmp = rotate[0]; for (var i = 0; i < rotate.Length - 1; i++) { rotate[i] = rotate[i + 1]; } rotate[rotate.Length - 1] = tmp; return rotate; } /// /// Rotates Array to the right /// /// Data /// Copy of data public byte[] rotateRight(byte[] data) { byte[] rotate = new byte[data.Length]; data.CopyTo(rotate, 0); byte tmp = rotate[rotate.Length - 1]; for (var i = rotate.Length - 1; i > 0; i--) { rotate[i] = rotate[i - 1]; } rotate[0] = tmp; return rotate; } /// /// Concatenates two Arrays, Array A start at index 0 /// /// Array A /// Array B /// Copy of Data public byte[] concatenate(byte[] a, byte[] b) { byte[] c = new byte[a.Length + b.Length]; a.CopyTo(c, 0); b.CopyTo(c, a.Length); return c; } public byte[] xor(byte[] a, byte[] b) { if(a.Length != b.Length) { throw new ArgumentException("Array are not same Length"); } byte[] c = new byte[a.Length]; for(int i = 0; i < a.Length; i++) { c[i] = (byte)(a[i] ^ b[i]); } return c; } #endregion #endregion #region DESFire Commands /// /// Get Application IDs from Card /// /// AIDs as Array public UInt32[] GetApplicationIDs() { APDUCommand cmd = new APDUCommand(IsoCase.Case2Short) { CLA = 0x90, INS = (byte)APDUInstructions.GET_APPLICATION_IDS }; APDUResponse response = _Card.Transmit(cmd); CheckAPDUResponse(response); if (response.Body.Length % 3 != 0) { throw new Exception(string.Format("Invalid body length (was: {0}).", response.Body.Length)); } if(response.Body.Length == 0) { throw new Exception("Missing PICC Entry 0x000000."); } List applicationIDs = new List(); for (int i = 0; i < response.Body.Length; i += 3) { UInt32 new_applicationID = 0; new_applicationID = (UInt32)((response.Body[i] << 16) + (response.Body[i + 1] << 8) + response.Body[i + 2]); applicationIDs.Add(new_applicationID); } return applicationIDs.ToArray(); } /// /// Select Application by AID /// /// 3 Byte AID public void SelectApplication(UInt32 aid) { byte[] id_byte = BitConverter.GetBytes(aid); APDUCommand cmd = new APDUCommand(IsoCase.Case4Short) { CLA = 0x90, INS = (byte)APDUInstructions.SELECT_APPLICATION, Data = new byte[] { id_byte[0], id_byte[1], id_byte[2] } }; APDUResponse response = _Card.Transmit(cmd); CheckAPDUResponse(response); } #endregion /// /// Expand Array to Block Size /// /// /// public byte[] expandToBlockSize(byte[] data, int bocksize) { int diff = data.Length % 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; } public void ChangeKeyDES(byte key_no, byte[] key_new, byte[] key_current) { Console.WriteLine("key_new: {0}", ConvertToHexString(key_new)); key_new = concatenate(key_new, key_new); Console.WriteLine("key_new_double: {0}", ConvertToHexString(key_new)); byte[] header = new byte[] { 0xC4, key_no }; Console.WriteLine("header: {0}", ConvertToHexString(header)); byte[] cmd_ = concatenate(header, key_new); Console.WriteLine("cmd_: {0}", ConvertToHexString(cmd_)); CRC32 crc = new CRC32(); byte[] cmd_crc = crc.Calculate(cmd_); Console.WriteLine("cmd_crc: {0}", ConvertToHexString(cmd_crc)); byte[] cryptogram = concatenate(key_new, cmd_crc); Console.WriteLine("cryptogram: {0}", ConvertToHexString(cryptogram)); byte[] cryptogram_block = expandToBlockSize(cryptogram, 8); Console.WriteLine("cryptogram_block: {0}", ConvertToHexString(cryptogram_block)); DES des = new DES(); byte[] cryptogram_block_enc = des.Encrypt(cryptogram_block, _SessionKey, _IV); Console.WriteLine("cryptogram_block_enc: {0}", ConvertToHexString(cryptogram_block_enc)); byte[] data = concatenate(new byte[] { key_no }, cryptogram_block_enc); Console.WriteLine("data: {0}", ConvertToHexString(data)); APDUCommand cmd = new APDUCommand(IsoCase.Case4Short) { CLA = 0x90, INS = 0xC4, Data = data }; Console.WriteLine("cmd: {0}", ConvertToHexString(cmd.ToArray())); APDUResponse response = _Card.Transmit(cmd); CheckAPDUResponse(response); } /// /// Create new Application with AID /// /// Appilication ID public APDUCommand CreateApplication(UInt64 aid) { throw new NotImplementedException(); } public void Format() { throw new NotImplementedException(); } /// /// Authenticate to Card /// /// 0x01 - 0x0D /// // TODO public void AuthenticateDES(byte key_id, byte[] key) { APDUCommand cmd_challange_request = new APDUCommand(IsoCase.Case4Short) { CLA = 0x90, INS = (byte)0x1A, Data = new byte[] { key_id } }; APDUResponse response = _Card.Transmit(cmd_challange_request); byte[] rndB_enc = response.Body; Console.WriteLine("rndB_enc: {0}", ConvertToHexString(rndB_enc)); DES des = new DES(); byte[] rndB = des.Decrypt(rndB_enc, key, GenerateDefaultKey(8)); Console.WriteLine("rndB: {0}", ConvertToHexString(rndB)); byte[] iv = new byte[8]; rndB.CopyTo(iv, 0); byte[] rndB_rl = rotateLeft(rndB); Console.WriteLine("rndB_enc: {0}", ConvertToHexString(rndB_rl)); byte[] rndA = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; Console.WriteLine("rndA: {0}", ConvertToHexString(rndA)); byte[] rndAB = concatenate(rndA, rndB_rl); Console.WriteLine("rndAB: {0}", ConvertToHexString(rndAB)); byte[] rndAB_enc = des.Encrypt(rndAB, key, rndB_enc); Console.WriteLine("rndA_rndB_enc: {0}", ConvertToHexString(rndAB_enc)); iv = GetLastBlock(rndAB_enc); APDUCommand cmd_challange_response = new APDUCommand(IsoCase.Case4Short) { CLA = 0x90, INS = (byte)0xAF, Data = rndAB_enc }; Console.WriteLine("cmd_challange_response: {0}", ConvertToHexString(cmd_challange_response.ToArray())); response = _Card.Transmit(cmd_challange_response); byte[] encryptedRndAFromCard = response.Body; Console.WriteLine("encryptedRndAFromCard: {0}", ConvertToHexString(encryptedRndAFromCard)); byte[] rotatedRndAFromCard = des.Decrypt(encryptedRndAFromCard, key, iv); Console.WriteLine("rotatedRndAFromCard: {0}", ConvertToHexString(rotatedRndAFromCard)); byte[] rndAFromCard = rotateRight(rotatedRndAFromCard); Console.WriteLine("rndAFromCard: {0}", ConvertToHexString(rndAFromCard)); if (!rndA.SequenceEqual(rndAFromCard)) { throw new Exception("PICC Challenge is not correct answered."); } _SessionKey = GenerateDESSesionKey(rndA, rndB); _IV = GenerateDefaultKey(8); } private byte[] GenerateDESSesionKey(byte[] rndA, byte[] rndB) { byte[] sesssionkey = new byte[8]; for(int i = 0; i < sesssionkey.Length; i++) { if(i < 4) { sesssionkey[i] = rndA[i]; } else { sesssionkey[i] = rndB[i - 4]; } } // Set Key Verion of Key to 0x55 to LSB // TODO byte[] key_version = SetKeyVersion(sesssionkey, 0x55); return concatenate(key_version, key_version); } public byte[] SetKeyVersion(byte[] key, byte keyversion) { byte[] pow2 = new byte[] { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }; byte[] new_key = new byte[key.Length]; key.CopyTo(new_key, 0); for(int i = 0; i < 8; i++) { if((keyversion & pow2[i]) > 0) { new_key[i] = (byte)(new_key[5] | 0x01); } else { new_key[i] = (byte)(new_key[5] & 0x7F); } } return new_key; } public void ChangeApplicationMasterKey(byte[] aPP_MasterKey) { throw new NotImplementedException(); } /// /// Delete Application by ID /// /// 3 Byte ID public void DeleteApplication(UInt32 id) { byte[] id_byte = BitConverter.GetBytes(id); APDUCommand cmd = new APDUCommand(IsoCase.Case4Short) { CLA = 0x90, INS = (byte)APDUInstructions.DELETE_APPLICATION, Data = new byte[] { id_byte[0], id_byte[1], id_byte[2] }, Le = 0x00 }; APDUResponse response = _Card.Transmit(cmd); CheckAPDUResponse(response); } public void ChangeApplicationKey(int v, byte[] aPP_Key_1) { throw new NotImplementedException(); } /// /// Select Application by ID /// /// 3 Byte ID public APDUCommand CreateApplication(UInt32 id, byte keysetting1, byte keysetting2) { byte[] id_byte = BitConverter.GetBytes(id); APDUCommand cmd = new APDUCommand(IsoCase.Case4Short) { CLA = 0x90, INS = (byte)APDUInstructions.CREATE_APPLICATION, Data = new byte[] { id_byte[0], id_byte[1], id_byte[2], keysetting1, keysetting2 }, Le = 0x00 }; return cmd; } public void CreateFile(byte fabAccessIdentFileID, FileCommunication pLAIN, ushort fileAccessRight, UInt32 v) { throw new NotImplementedException(); } public ushort GenerateFileAccessRight(AccessRights fREE, int v1, int v2, int v3) { throw new NotImplementedException(); } public void WirteData(byte fabAccessIdentFileID, int v1, int v2, byte[] vs) { throw new NotImplementedException(); } public void CreateFile(byte fabAccessIdentFileID, object plain, AccessRights fREE, int v1, int v2, int v3) { throw new NotImplementedException(); } public byte[] ReadData(byte identFileID, int v1, int v2) { throw new NotImplementedException(); } /// /// Genearte KeySetting1 for Application Settings or PICC Setting /// public byte GenerateKeySetting1(ChangeApplicationKey changeKey, ChangeMasterKeySettings changeMasterKeySettings, CreateDeleteFile createDeleteFile, FileDirectoryAccess fileDirectoryAccess, ChangeMasterKey changeMasterKey) { return (byte)(((byte)changeKey << 4) | (byte)changeMasterKeySettings | (byte)createDeleteFile | (byte)fileDirectoryAccess | (byte)changeMasterKey); } } }