[.NET][C#]Parse ISO8583筆記(八)晶片卡交易AC(ARQC)運算

ISO8583格式中會傳遞許多卡片敏感性的資訊,為確保並鑑定訊息來源正確及完整,筆記ISO8583格式中第三種晶片交易的保護機制:

  • MAC(ISO9807、CNS13526)
  • PIN Block(ISO9564 - 1、CNS13798)
  • ARQC/ARPC

 

每一筆晶片卡的授權交易請求都會包含ARQC,就像是交易的數位簽章或是校對碼(checksum),可以確保交易產生端的合法性以及交易內容的完整性,概念很像ISO8583 DE64的MAC,MAC是一定期間內固定的工作基碼運算出來,但ARQC/ARPC則是每張晶片卡唯一甚至每筆交易唯一的基碼對交易資訊作運算取得的驗證值。


發卡機構處理完授權交易請求後,接著開始產生回應,類似ARQC的作法,發卡主機也產生ARPC來讓晶片確認交易回應端的合法性以及交易內容的完整性,
ARPC下一篇再筆記,這篇先寫完ARQC。

T表示交易相關資訊,像是消費金額、交易日期等。

ARQC是英文Authorisation ReQuest Cryptogram的簡稱,如下圖,由卡片晶片內的程式收到刷卡機Generate AC請求而產生,驗證值透過ISO8583 DE55晶片資訊Tag 9F26上傳到發卡機構進行授權認證,再由發卡端的程式驗證內容正確性。

註:晶片資料會用BER-TLV編碼放在ISO8583 DE55,不過國內代理清算則不太相同,如果想Parse標準DE55,可以參考先前這篇TLV筆記

 

 

目前AC驗證機制共有4個版本VSDC、M/Chip、EMV4.1及EMV4.2,運算的過程有一些差異,不過大致的步驟如下:

  • 步驟1:取得Card Key 
  • 步驟2:使用Card Key產生Session Key
  • 步驟3:準備ARQC運算的輸入資料
  • 步驟4:以MAC演算法運算出ARQC

 

 

步驟1: 取得Card Key 

Card Key也稱為UDK(Unique Derived Key)或ICC Master Key,為了確保每張卡片都有唯一性,產生Card key的過程會將PAN(卡號)及發卡序號(通常為00)作為明文並以MDK基碼作2TDEA運算。

根據EMV_Book_2 附錄A1.4 Master Key Derivation 有兩種計算方式,但卡號長度<=16時用Option A,台灣境內發行的卡片都是16碼(含)以內,這邊我們先用Option A:

Z := (ZL || ZR),計算兩組8yte的字串,最後串接後成為Card Key。

演算法內容: 
ZL:= DES3(MDK)[Y]
ZR:= DES3(MDK)[Y ⊕ ('FF'||'FF'||'FF'||'FF'||'FF'||'FF'||'FF'||'FF')]

*Y: 卡號(PAN)及發卡序號(通常為00),從右邊往左取16位

根據EMV規格,產生出來的Card Key(ICC Master Key)必須做Odd Parity check 奇數同位檢查位元,最後輸出16Byte Card Key。

1.準備輸入資料

MDK 0123456789ABCDEFFEDCBA9876543210
PAN 4219876543210987
PAN Seq 00
Y= 1987654321098700

 

2.撰寫一段Triple DES Encryption Method

public static byte[] TEncryption(byte[] Deskey, byte[] plainText)
{
    SymmetricAlgorithm TdesAlg = new TripleDESCryptoServiceProvider();
    //設定基碼
    TdesAlg.Key = Deskey;
    //加密工作模式:CBC
    TdesAlg.Mode = CipherMode.CBC;
    //補充字元方式:0
    TdesAlg.Padding = PaddingMode.Zeros;
    //初始向量IV = 0
    TdesAlg.IV = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    ICryptoTransform ict = TdesAlg.CreateEncryptor(TdesAlg.Key, TdesAlg.IV);
    MemoryStream mStream = new MemoryStream();
    CryptoStream cStream = new CryptoStream(mStream, ict, CryptoStreamMode.Write)
    cStream.Write(plainText, 0, plainText.Length);
    cStream.FlushFinalBlock();
    cStream.Close();
    return mStream.ToArray();
}

 

3.準備Parity CHeck Method(Odd and Even)

//奇數檢查位元(Odd Parity):設定同位檢查位元,以便位元集計數為奇數。
public static byte[] OddParity(byte[] DesKey)
{
    for (var i = 0; i < DesKey.Length; ++i)
    {
        int keyByte = DesKey[i] & 0xFE;
        var parity = 0;
        for (int b = keyByte; b != 0; b >>= 1)
        {
            parity = parity ^ b & 1;
        }
        DesKey[i] = (byte)(keyByte | (parity == 0 ? 1 : 0));
    }
    return DesKey;
}
//偶數檢查位元(Even Parity):設定同位檢查位元,以便位元集計數為偶數。
public static byte[] EvenParity(byte[] DesKey)
{
    for (var i = 0; i < DesKey.Length; ++i)
    {
        int keyByte = DesKey[i] & 0xFE;
        var parity = 0;
        for (int b = keyByte; b != 0; b >>= 1)
        {
            parity = parity ^ b & 1;
        }
        DesKey[i] = (byte)(keyByte | (parity == 0 ? 0 : 1));
    }
    return DesKey;
}

 

4.準備測試方法

//產生Card Key(或稱UDK、ICC Master Key)
[TestMethod]
public void TestGenCardKey()
{
    //MDK A + MDK B
    string MDK = "0123456789ABCDEFFEDCBA9876543210";
    //PAN
    string PAN = "4219876543210987";
    //PAN SEQ(如果銀行沒有使用,就放00)
    string PANSeq = "00";
    //取出明文資料
    string Y = (PAN + PANSeq).Right(16);
    Console.WriteLine("Y:{0}", Y);

    //取得Z(Left Key)加密演算結果 ZL:= DES3(MDK)[Y]
    string ZL = TEncryption(MDK.HexToByte(), Y.HexToByte()).BToHex();

    //取得Z(Right Key)加密演算結果 ZR:= DES3(MDK)[Y ⊕ ('FF'||'FF'||'FF'||'FF'||'FF'||'FF'||'FF'||'FF')]
    string ZR = TEncryption(MDK.HexToByte(), XOR(Y.HexToByte(), "FFFFFFFFFFFFFFFF".HexToByte())).BToHex();

    //CardKey組成
    Console.WriteLine("Card Key ZL:{0}", ZL);
    Console.WriteLine("Card Key ZR:{0}", ZR);

    //1.NONE
    Console.WriteLine("Card Key Z(KEY parity=none):{0}", ZL + ZR);
    Console.WriteLine("");
    //2.Odd(奇數檢查位元)確保1的數目是奇數
    Console.WriteLine("Card Key Z(KEY parity=Odd) :{0}", OddParity((ZL + ZR).HexToByte()).BToHex());
    Console.WriteLine("Card Key Z(KEY parity=Odd) :9249345E0220CEBA0D20D6A2453BF407(BP-Tool)");
    Console.WriteLine("");
    //3.Even(偶數檢查位元)確保1的數目是偶數
    Console.WriteLine("Card Key Z(KEY parity=Even):");
    Console.WriteLine("Card Key Z(KEY parity=Eve) :{0}", EvenParity((ZL + ZR).HexToByte()).BToHex());
    Console.WriteLine("Card Key Z(KEY parity=Eve) :9348355F0321CFBB0C21D7A3443AF506(BP-Tool)");
}

測試結果: 與BP-Tool相符!

取得Card Key(UDK): 9249345E0220CEBA0D20D6A2453BF407

 

 

步驟2:使用Card Key產生Session Key

以第一步驟取得的UDK:9249345E0220CEBA0D20D6A2453BF407練習產生Session Key

為了確保每張卡片的每一筆交易有唯一性,根據EMV組織的EMV_Book_2 附錄A1.3 Session Key Derivation,產生Session key的過程將使用來自晶片卡內的ATC及來自刷卡機內的UN一起作為明文並以Card Key(UDK)基碼作2TDEA運算
 

*目前VSDC、M/Chip與EMV4.1/4.2產生Session key有一些差異,VSDC甚至可以跳過這個步驟直接以Card key作為Session key,這邊先以M/Chip為主。

輸入明文 (ICC Data and Terminal data)

  • 9F36: Application Transaction Counter (ATC) From ICC
  • 9F37: Unpredictable Number (UN) From Terminal

 

演算法內容: 

兩段明文的作法分別用'F0'及'0F'區隔ATC及UN,然後分別運算後組合而成。
SKL := DES3 (MK) [(R0 || R1 || 'F0' || R3 || R4 || R5 || R6 || R7)]
SKR := DES3 (MK) [(R0 || R1 || '0F' || R3 || R4 || R5 || R6 || R7)]
SK := SKL || SKR

1.準備輸入資料

UDK 9249345E0220CEBA0D20D6A2453BF407
ATC 0001
UN 30901B6A

2.準備測試方法

//產生SK(Session key)
[TestMethod]
public void TestGenSessionKey()
{
    //步驟2: 使用Card Key產生Session Key
    string UDK = "9249345E0220CEBA0D20D6A2453BF407";
    string ATC = "0001";
    string UN = "30901B6A";
    string RL = string.Format("{0}F000{1}", ATC, UN);
    string RR = string.Format("{0}0F00{1}", ATC, UN);
    string SL = TEncryption(UDK.HexToByte(), RL.HexToByte()).BToHex();
    string SR = TEncryption(UDK.HexToByte(), RR.HexToByte()).BToHex();

    Console.WriteLine("Session Key SL:{0}", SL);
    Console.WriteLine("Session Key SR:{0}", SR);
    Console.WriteLine("Session Key S:{0}{1}", SL, SR);
    Console.WriteLine("Session Key S:E57C032E6FE8AA94D0D72FF8E4D4DFBC(BP-Tool)");
}

測試結果: 與BP-Tool相符!

取得Session key:E57C032E6FE8AA94D0D72FF8E4D4DFBC

 

步驟3:準備ARQC運算的輸入資料

為了確保交易在傳遞過程中沒有被竄改,EMV_Book_2 8.1.1 Data Selection章節定義了基本的明文組成:
8個來自刷卡機的資料以及3個來自ICC晶片卡內的重要交易資訊欄位。

EMV Tag Description From
9F02 Amount, Authorised (Numeric) Terminal 
9F03 Amount, Other (Numeric) Terminal 
9F1A Terminal Country Code Terminal 
95 Terminal Verification Results (TVR) Terminal 
5F2A Transaction Currency Code(ISO 4217) Terminal 
9A Transaction Date(YYMMDD) Terminal 
9C Transaction Type:Terminal Terminal 
9F37 Unpredictable Number (UN) Terminal 
82 Application Interchange Profile (AIP) ICC
9F36 Application Transaction Counter (ATC) ICC
9F10 Issuer Application Data (IAD) ICC


如果還記得先前ISO8583 DE64 MAC,可以相比較MAC是拿卡號(PAN)、處理代碼(P code)、交易金額、追蹤號碼(Trace number)及交易時間。

 

這邊簡單用字典來裝並放測試資料,如果有時間再改寫成Class

static Dictionary<string, string> CDOL1 = new Dictionary<string, string>()
{
    {"Emv9F02","000000001000" },   //Amount, Authorised (Numeric):Terminal 
    {"Emv9F03","000000000000"},    //Amount, Other (Numeric):Terminal
    {"Emv9F1A","0710"},            //Terminal Country Code:Terminal
    {"Emv95","0000000000"},        //Terminal Verification Results (TVR):Terminal
    {"Emv5F2A","0710"},            //Transaction Currency Code(ISO 4217):Terminal
    {"Emv9A","130205"},            //Transaction Date(YYMMDD):Terminal
    {"Emv9C","00"},                //Transaction Type:Terminal:Terminal
    {"Emv9F37","30901B6A"},        //Unpredictable Number (UN):Terminal
    {"Emv82","3C00"},              //Application Interchange Profile (AIP):ICC
    {"Emv9F36","0055"},            //Application Transaction Counter (ATC):ICC
    {"Emv9F10","03A4A082"}         //Issuer Application Data (IAD):ICC
};

 

填充字元:明文組成結果如果長度非16位數倍數時,自動在明文右方填補字元(padding ISO9797 Method2)

MSG:= (MSG || '80' || '00' || '00' ||...

 

步驟4:以MAC演算法運算出ARQC

MAC(ISO9797-1 MAC Algorithm 3): 

Hi:= ALG(KSL)[Xi ⊕ Hi - 1], for i = 1, 2, . . . , k 
將明文以16Byte為單位切割成幾塊block,依序將每塊block先和前一次結果做⊕運算後以session key(Left)為基碼再進行Single DES運算。

最後一塊在MAC運算後的選項:

Hi+1 := ALG(KSL)[ALG-1(KSR)[Hi]]
MAC運算結果以session key(Right)為基碼進行DES解密運算再以session key(Left)進行為基碼再進行DES加密運算。

 

1.撰寫一段Single DES Encryption Method

public static byte[] Encryption(byte[] Deskey, byte[] plainText)
{
    SymmetricAlgorithm desAlg = new DESCryptoServiceProvider();
    //設定基碼
    desAlg.Key = Deskey;
    //加密工作模式:CBC
    desAlg.Mode = CipherMode.CBC;
    //補充字元方式:0
    desAlg.Padding = PaddingMode.Zeros;
    //初始向量IV = 0
    desAlg.IV = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    ICryptoTransform ict = desAlg.CreateEncryptor(desAlg.Key, desAlg.IV);
    MemoryStream mStream = new MemoryStream();
    CryptoStream cStream = new CryptoStream(mStream, ict, CryptoStreamMode.Write);
    cStream.Write(plainText, 0, plainText.Length);
    cStream.FlushFinalBlock();
    cStream.Close();
    return mStream.ToArray();
}

2.撰寫一段Single DES Decryption Method

public static byte[] Decryption(byte[] Deskey, byte[] plainText)
{
    SymmetricAlgorithm desAlg = new DESCryptoServiceProvider();
    //設定基碼
    desAlg.Key = Deskey;
    //加密工作模式:CBC
    desAlg.Mode = CipherMode.CBC;
    //補充字元方式:0
    desAlg.Padding = PaddingMode.Zeros;
    //初始向量IV = 0
    desAlg.IV = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    ICryptoTransform ict = desAlg.CreateDecryptor(desAlg.Key, desAlg.IV);
    MemoryStream mStream = new MemoryStream();
    CryptoStream cStream = new CryptoStream(mStream, ict, CryptoStreamMode.Write);
    cStream.Write(plainText, 0, plainText.Length);
    cStream.FlushFinalBlock();
    cStream.Close();
    return mStream.ToArray();
}

3.準備XOR ⊕ Method

public static byte[] XOR(byte[] bHEX1, byte[] bHEX2)
{
    byte[] bHEX_OUT = new byte[bHEX1.Length];
    for (int i = 0; i < bHEX1.Length; i++)
    {
        bHEX_OUT[i] = (byte)(bHEX1[i] ^ bHEX2[i]);
    }
    return bHEX_OUT;
}

4.撰寫MAC演算法(XOR+ DES 循環運算)

public static string MAC(string DesKey, string plainText)
{
    //EMV_Book_2 A1.2 Message Authentication Code(ISO9797)
    //Hi := ALG(KSL)[Xi ⊕ Hi-1], for i = 1, 2, . . . , k
    List<string> plainTexts = (from Match m in Regex.Matches(plainText, @"\w{16}")
                               select m.Value).ToList();

    //從第1段明文開始
    byte[] CipherText = plainTexts[0].HexToByte();
    for (int i = 0; i < plainTexts.Count; i++)
    {
        if (i > 0)
        {
            CipherText = XOR(plainTexts[i].HexToByte(), CipherText);
        }
        Console.WriteLine("{0} time XOR: {1}", i + 1, CipherText.BToHex());
        CipherText = Encryption(DesKey.HexToByte(), CipherText);
        Console.WriteLine("{0} time Encryption: {1}", i + 1, CipherText.BToHex());
    }
    return CipherText.BToHex();
}

 

5.準備測試程式

[TestMethod]
public void TestGenARQC()
{
    //Session Key
    string SL = "E57C032E6FE8AA94"; 
    string SR = "D0D72FF8E4D4DFBC";
    string SK = SL + SR;

    //組合明文
    StringBuilder sbCPOL1 = new StringBuilder();
    foreach (var item in CDOL1)
    {
        sbCPOL1.Append(item.Value);
    }
    Console.WriteLine("SK :{0}", SK);

    //位數不足用'80' '00' '00' ..補
    string bpCPOL1 = "0000000010000000000000000710000000000007101302050030901B6A3C00005503A4A082800000";
    Console.WriteLine("sbCPOL1 :Length:{0},data:{1}", sbCPOL1.ToString().Length, sbCPOL1.ToString());
    Console.WriteLine("sbCPOL1 :Length:{0},data:{1}(BP Tool)", bpCPOL1.Length, bpCPOL1.ToString());

    //將明文資料作MAC運算 
    byte[] Hk = MAC(SL, bpCPOL1).HexToByte();

    //ISO/IEC 9797-1 Algorithm 3:
    //Hk+1 := ALG(KSL)[ALG-1(KSR)[Hk]]
    Hk = Decryption(SR.HexToByte(), Hk);
    Hk = Encryption(SL.HexToByte(), Hk);

    Console.WriteLine("ARQC(step4-2) :{0}(ISO/IEC 9797-1 Algorithm 3)", Hk.BToHex());
    Console.WriteLine("ARQC(step4-2) :{0}(BP Tool)", "6BC76F457CC4FB24");
}

測試結果: 與BP-Tool相符! ARQC = 6BC76F457CC4FB24

小結:

  • ARQC: ISO8583 Emv Tag 9F26 Application Cryptogram (AC)。

 

參考:

EMV Wiki

EMV_Book_1 EMV_Book_2