CPU卡校驗MAC1、計算MAC2、校驗TAC的方式及流程
阿新 • • 發佈:2019-01-08
前言
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,