1. 程式人生 > >CPU卡校驗MAC1、計算MAC2、校驗TAC的方式及流程

CPU卡校驗MAC1、計算MAC2、校驗TAC的方式及流程

前言

CPU卡的各個金鑰是需要通過加密機分散獲得,因此這裡使用的是之前自己發的一張復旦微電子的測試卡,現已經明確知道其對應的充值金鑰及維護金鑰,想了解簡單的髮卡流程可以參考之前的部落格:

復旦微電子CPU卡髮卡流程

具體流程直接上程式碼,裡面有比較詳細的註釋,按著程式碼一行一行的讀就可以了

mac1驗證、mac2計算、tac驗證流程

import java.util.Locale;

public class CardCenter
{
    public static void main(String[] args)
    {

        // 圈存的key
        String loadKey = "3F013F013F013F013F013F013F013F01";
        System.out.println("圈存的key:" + loadKey);
        // 驗證tac的key
        String tacKey = "34343434343434343434343434343434"
; System.out.println("驗證tac的key:" + tacKey); System.out.println(); // posid String posid = "112233445566"; System.out.println("終端ID:" + posid); // 交易金額 String tradeAmount = "00000001"; System.out.println("交易金額:" + tradeAmount); // 交易金額十進位制 int ta = 1
; // 交易型別 String tradeType = "02"; System.out.println("交易型別:" + tradeType); System.out.println(); // 預充值指令 System.out.println("組裝預充值指令:805000020b0100000001112233445566"); // 預消費指令805000020b0100000001112233445566的指令回覆 String preTopup = "0000001a0017000106b825d7684c81ce9000"; System.out.println("得到預充值響應:" + preTopup); byte[] recvByte = ByteUtil.hexStr2Byte(preTopup); // 卡餘額 String balance = ByteUtil.hexToStr(recvByte, 0, 4); int bal = ByteUtil.hexToInt(recvByte, 0, 4); System.out.println("卡餘額:" + balance); // 聯機計數器 String cardCnt = ByteUtil.hexToStr(recvByte, 4, 2); System.out.println("聯機計數器:" + cardCnt); // 金鑰版本 String keyVersion = ByteUtil.hexToStr(recvByte, 6, 1); System.out.println("金鑰版本:" + keyVersion); // 演算法標識 String alglndMark = ByteUtil.hexToStr(recvByte, 7, 1); System.out.println("演算法標識:" + alglndMark); // 隨機數 String random = ByteUtil.hexToStr(recvByte, 8, 4); System.out.println("隨機數:" + random); // mac1 String mac1 = ByteUtil.hexToStr(recvByte, 12, 4); System.out.println("mac1:" + mac1); System.out.println(""); System.out.println("開始驗證mac1"); // 驗證mac1的正確性 // 輸入的資料為:隨機數+聯機計數器+“8000” String inputData = random + cardCnt + "8000"; System.out.println("計算過程金鑰資料:" + inputData); // 計算過程金鑰 String sessionKey = Des.getHintKey(inputData, loadKey); System.out.println("過程金鑰:" + sessionKey); // 計算mac1需要輸入的資料 // 輸入的資料為:餘額+交易金額+交易型別+終端編號 inputData = balance + tradeAmount + tradeType + posid; String legitMac1 = Des.PBOC_DES_MAC(sessionKey, "0000000000000000", inputData, 0).substring(0, 8); System.out.println("標準的mac1資料:" + legitMac1); if (!mac1.toUpperCase(Locale.getDefault()).equals(legitMac1)) { System.out.println("mac1校驗失敗!"); return; } System.out.println("mac1校驗成功!"); // 開始計算Mac2用於做充值確認操作 System.out.println(""); System.out.println("開始計算mac2"); // 交易日期 String tradeDate = "20170310"; // 交易時間 String tradeTime = "110734"; // 計算mac2的輸入資料 // 輸入資料為:交易金額+交易型別+終端編號+交易時間+交易日期 inputData = tradeAmount + tradeType + posid + tradeDate + tradeTime; String mac2 = Des.PBOC_DES_MAC(sessionKey, "0000000000000000", inputData, 0).substring(0, 8); // 得到mac2 System.out.println("計算後的mac2:" + mac2); // 組裝寫卡指令 System.out.println("805200000b" + tradeDate + tradeTime + mac2); System.out.println("開始向卡片傳送充值確認的指令"); // 向卡傳送指令 System.out.println("apdu:805200000b201703101107349C8A0625"); // 響應tac System.out.println("recv:16ead5169000"); String tac = "16ead516"; System.out.println("得到Tac:" + tac); System.out.println(""); System.out.println("開始驗證tac"); System.out.println("tac驗證金鑰:" + tacKey); // 對tac驗證金鑰左邊8個位元組和右邊8個位元組做異或處理得到tac過程金鑰 String tacTessionKey = Des.xOr(tacKey.substring(0, 16), tacKey.substring(16, 32)); System.out.println("tac過程金鑰:" + tacTessionKey); // 充值成功之後新的金額為00000043+00000001=00000044 String newBalance = ByteUtil.hexToStr(ByteUtil.intToHex(bal + ta, 4)); // 計算標準的tac的輸入資料 // 輸入的資料:新的餘額+舊的聯機計數器+交易金額+交易型別+終端編號+交易日期+交易時間 inputData = newBalance + cardCnt + tradeAmount + tradeType + posid + tradeDate + tradeTime; String legitTac = Des.PBOC_DES_MAC(tacTessionKey, "0000000000000000", inputData, 0).substring(0, 8); System.out.println("標準的tac資料:" + legitTac); if (!tac.toUpperCase(Locale.getDefault()).equals(legitTac)) { System.out.println("tac校驗錯誤!"); return; } System.out.println("tac校驗成功!"); } }

執行結果如下

圈存的key:3F013F013F013F013F013F013F013F01
驗證tac的key:34343434343434343434343434343434

終端ID:112233445566
交易金額:00000001
交易型別:02

組裝預充值指令:805000020b0100000001112233445566
得到預充值響應:0000001a0017000106b825d7684c81ce9000
卡餘額:0000001a
聯機計數器:0017
金鑰版本:00
演算法標識:01
隨機數:06b825d7
mac1:684c81ce

開始驗證mac1
計算過程金鑰資料:06b825d700178000
過程金鑰:6E76E8541E217D9A
D[0]=0000001a00000001
D[1]=0211223344556680
1**************
I=0000001A00000001
O=D42EE2FF647C9F00
I=D63FC0CC2029F980
I=684C81CE47609B5B
標準的mac1資料:684C81CE
mac1校驗成功!

開始計算mac2
D[0]=0000000102112233
D[1]=4455662017031011
D[2]=0734800000000000
1**************
I=0000000102112233
O=A0D9EACF4A0A833D
I=E48C8CEF5D09932C
2**************
I=E48C8CEF5D09932C
O=6DF657FD31FBAB05
I=6AC2D7FD31FBAB05
I=9C8A06256ACBF60A
計算後的mac2:9C8A0625
805200000b201703101107349C8A0625
開始向卡片傳送充值確認的指令
apdu:805200000b201703101107349C8A0625
recv:16ead5169000
得到Tac:16ead516

開始驗證tac
tac驗證金鑰:34343434343434343434343434343434
tac過程金鑰:0000000000000000
D[0]=0000001b00170000
D[1]=0001021122334455
D[2]=6620170310110734
D[3]=8000000000000000
1**************
I=0000001B00170000
O=F0DE01AF2D71051B
I=F0DF03BE0F42414E
2**************
I=F0DF03BE0F42414E
O=044D5B737F9A0282
I=626D4C706F8B05B6
3**************
I=626D4C706F8B05B6
O=2B5ED4A6C483D0BF
I=AB5ED4A6C483D0BF
I=16EAD5165389360A
標準的tac資料:16EAD516
tac校驗成功!

涉及的幫助類

DES工具類
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

/**
 * DES工具類
 */
public class Des
{
    /**
     * ***************************壓縮替換S-Box*************************************
     * ************
     */
    private static final int[][] s1 =
    {
    { 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7 },
    { 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8 },
    { 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0 },
    { 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 } };
    private static final int[][] s2 =
    {
    { 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10 },
    { 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5 },
    { 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15 },
    { 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 } };
    private static final int[][] s3 =
    {
    { 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8 },
    { 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1 },
    { 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7 },
    { 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 } };
    private static final int[][] s4 =
    {
    { 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15 },
    { 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9 },// erorr
            { 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4 },
            { 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 } };
    private static final int[][] s5 =
    {
    { 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9 },
    { 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6 },
    { 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14 },
    { 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 } };
    private static final int[][] s6 =
    {
    { 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11 },
    { 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8 },
    { 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6 },
    { 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 } };
    private static final int[][] s7 =
    {
    { 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1 },
    { 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6 },
    { 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2 },
    { 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 } };
    private static final int[][] s8 =
    {
    { 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7 },
    { 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2 },
    { 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8 },
    { 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 } };
    private static final int[] ip =
    { 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25,
            17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 };
    private static final int[] _ip =
    { 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52,
            20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25 };
    // 每次金鑰迴圈左移位數
    private static final int[] LS =
    { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };
    private static int[][] subKey = new int[16][48];
    public static int HEX = 0;
    public static int ASC = 1;

    /**
     * 將十六進位制A--F轉換成對應數
     * 
     * @param ch
     * @return
     * @throws Exception
     */
    public static int getIntByChar(char ch) throws Exception
    {
        char t = Character.toUpperCase(ch);
        int i = 0;
        switch (t)
        {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                i = Integer.parseInt(Character.toString(t));
                break;
            case 'A':
                i = 10;
                break;
            case 'B':
                i = 11;
                break;
            case 'C':
                i = 12;
                break;
            case 'D':
                i = 13;
                break;
            case 'E':
                i = 14;
                break;
            case 'F':
                i = 15;
                break;
            default:
                throw new Exception("getIntByChar was wrong");
        }
        return i;
    }

    /**
     * 將字串轉換成二進位制陣列
     * 
     * @param source
     *            : 16位元組
     * @return
     */
    public static int[] string2Binary(String source)
    {
        int len = source.length();
        int[] dest = new int[len * 4];
        char[] arr = source.toCharArray();
        for (int i = 0; i < len; i++)
        {
            int t = 0;
            try
            {
                t = getIntByChar(arr[i]);
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
            String[] str = Integer.toBinaryString(t).split("");
            int k = i * 4 + 3;
            for (int j = str.length - 1; j > 0; j--)
            {
                dest[k] = Integer.parseInt(str[j]);
                k--;
            }
        }
        return dest;
    }

    /**
     * 返回x的y次方
     * 
     * @param x
     * @param y
     * @return
     */
    public static int getXY(int x, int y)
    {
        int temp = x;
        if (y == 0)
            x = 1;
        for (int i = 2; i <= y; i++)
        {
            x *= temp;
        }
        return x;
    }

    /**
     * s位長度的二進位制字串
     * 
     * @param s
     * @return
     */
    public static String binary2Hex(String s)
    {
        int len = s.length();
        int result = 0;
        int k = 0;
        if (len > 4)
            return null;
        for (int i = len; i > 0; i--)
        {
            result += Integer.parseInt(s.substring(i - 1, i)) * getXY(2, k);
            k++;
        }
        switch (result)
        {
            case 0:
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
            case 6:
            case 7:
            case 8:
            case 9:
                return "" + result;
            case 10:
                return "A";
            case 11:
                return "B";
            case 12:
                return "C";
            case 13:
                return "D";
            case 14:
                return "E";
            case 15:
                return "F";
            default:
                return null;
        }
    }

    /**
     * 將int轉換成Hex
     * 
     * @param i
     * @return
     * @throws Exception
     */
    public static String int2Hex(int i)
    {
        switch (i)
        {
            case 0:
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
            case 6:
            case 7:
            case 8:
            case 9:
                return "" + i;
            case 10:
                return "A";
            case 11:
                return "B";
            case 12:
                return "C";
            case 13:
                return "D";
            case 14:
                return "E";
            case 15:
                return "F";
            default:
                return null;
        }
    }

    /**
     * 將二進位制字串轉換成十六進位制字元
     * 
     * @param s
     * @return
     */
    public static String binary2ASC(String s)
    {
        String str = "";
        int ii = 0;
        int len = s.length();
        // 不夠4bit左補0
        if (len % 4 != 0)
        {
            while (ii < 4 - len % 4)
            {
                s = "0" + s;
            }
        }
        for (int i = 0; i < len / 4; i++)
        {
            str += binary2Hex(s.substring(i * 4, i * 4 + 4));
        }
        return str;
    }

    /**
     * IP初始置換
     * 
     * @param source
     * @return
     */
    public static int[] changeIP(int[] source)
    {
        int[] dest = new int[64];
        for (int i = 0; i < 64; i++)
        {
            dest[i] = source[ip[i] - 1];
        }
        return dest;
    }

    /**
     * IP-1逆置
     * 
     * @param source
     * @return
     */
    public static int[] changeInverseIP(int[] source)
    {
        int[] dest = new int[64];
        for (int i = 0; i < 64; i++)
        {
            dest[i] = source[_ip[i] - 1];
        }
        return dest;
    }

    /**
     * 2bit擴充套件8bit
     * 
     * @param source
     * @return
     */
    public static int[] expend(int[] source)
    {
        int[] ret = new int[48];
        int[] temp =
        { 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16,