最近公司在做pos业务,以前直接是pos接我们后台c,后台c去对接支付pos支付通道,现在打算用android系统pos机器,完成原pos机的业务,下面出2个图
传统pos和android系统pos
相信对支付业务了解的同学,都知道8583报文,我这个业务也是都是用是8583报文交互的,我们先来看看8583报文的组成
1,报文头
12,交易类型
3,位图
4,报文域
8583报文讲解网上一堆,我这主要是针对,8583组装和解析,已经常见的c传过来的数据转换位java字符串
8583报文是这样的下面举个例子:
60000000006016011603240210702406C022C09A111662257575445226840000000000000001000000102012051000100006326225757544522684D201220111172543433138383331303935323838383132303135383131303030303135365CB9C8D88C010844260000000000000001309F2608C26F69BE24700A5E9F2701809F101307010103A0B802010A010000000000BB1B528B9F37047A4DFFAC9F36020094950500880470009A031706089C01009F02060000000001005F2A02015682027C009F1A0201569F03060000000000009F3303E0E9C89F34034203009F3501229F1E0830303030303930359F4104000000100014220000010006204533383936303641
首先我们知道报文是16进制的一段数字串,可是通道c和我们前台java是scoket通信,scoket通信数据交互是byte数组,所以首先我们先要有byte[]
转化为16进制字符串的方法
* @param b 10进制byte数组
* @return 返回16进制的字符串
*/
public static String byte2Hex(byte[] b) {
String hs = "";
String stmp = "";
for (int n = 0; n < b.length; n++) {
stmp = (Integer.toHexString(b[n] & 0XFF));
if (stmp.length() == 1) {
hs = hs + "0" + stmp;
} else {
hs = hs + stmp;
}
if (n < b.length - 1)
hs = hs;
}
return hs.toUpperCase();
}
我们pos交易,常见的都是2个步骤
-
初始化-获取终端主密钥(此密钥受传输密钥保护)
-签到-获得工作密钥(此密钥受终端主密钥保护)
密钥是为了获得ping密钥来加密52数据(加密咱们银行卡交易密码)
mac密钥是来获得mac数据(64域数据)
先来讲解如何组装报文和解析报文,密钥的加解密穿插在里面讲,继续往下看
首先我先贴出我组装报文的方法
/**
* 组装报文(只支持位图为64的报文组装)
* @param data 数据
* @param type 交易类型
* @param bitmap String位图
public static byte[] makeBW(LinkedHashMap<String, String> data, String type, String bitmap) {
LogUtils.d("拼接报文每个域数据" + data.toString());
List<byte[]> result = new ArrayList<>();
//tpdu+MSGHEAD +type+bitmap
//Common.Messagehead=tpdu+MSGHEAD
String msghead = Common.Messagehead + type + PartsUtils.GroupOfBitMap(bitmap);
byte[] bytes1 = PartsUtils.GroupOfBCD(msghead);
result.add(bytes1);
for (Map.Entry<String, String> entry : data.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
String yuType = map1().get(key);
String yuLength = map2().get(key);
byte[] bytes = null;
if (yuLength.startsWith("LLVAR")) {
if (yuType.startsWith("BCD")) {
bytes = PartsUtils.GroupOfLLVar(value, Common.BCD);
} else {
bytes = PartsUtils.GroupOfLLVar(value, Common.ASCII);
}
} else if (yuLength.startsWith("LLLVAR")) {
if (yuType.startsWith("BCD")) {
bytes = PartsUtils.GroupOfLLLVar(value, Common.BCD);
} else {
bytes = PartsUtils.GroupOfLLLVar(value, Common.ASCII);
}
} else {
if (yuType.startsWith("BCD")) {
bytes = PartsUtils.GroupOfBCD(value);
} else {
bytes = PartsUtils.GroupOfASCII(value);
}
}
result.add(bytes);
}
byte[] bw_result = PartsUtils.ArrayMerge(result);
Log.d("BWTools", "报文组装数据:" + byte2Hex(bw_result));
return bw_result;
}
由于Messagehead一般不会变我就写在定义在成常量不;
相信大家都看到了很多类型什么LLLVAR和BCD之类的数据类型,这些都是报文64域中每一域的数据结构
我们组装报文肯定希望直接传个map就可以了key对应的是多少域,value直接就是该域的值,顺这个思路我的报文组装方法就是这样的,首先我们需要知道报文一共几种类型,就建立几种方法,其实就是把字符串转化为16进制的lllvar或者bcd类似的数据
下面是转换方法
生成位图的方法
/**
* @param bitmap 位图域
* @ 生成位图
/
public static String GroupOfBitMap(String bitmap) {
int length = bitmap.length() / 4;
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
String sub = bitmap.substring(i * 4, i * 4 + 4);
int d = Integer.parseInt(sub, 2);
sb.append(Integer.toHexString(d));
}
return sb.toString().toUpperCase();
}
传入位图生成报文所需要的位图如传入
//消费位图 64域中有数的域就置1没有数据置0
public final static String consume_bitmap =
"0111" + "0000" + "0010" + "0100" //1-16
+ "0000" + "0110" + "1100" + "0000" //17-32
+ "0010" + "0000" + "1100" + "0000" //33-48
+ "1001" + "1010" + "0001" + "0001";//49-64
把数据转化为BCD的报文
/*
* @param data 需要转换的数据
* @ BCD类型的报文
/
public static byte[] GroupOfBCD(String data) {
StringBuffer sb = new StringBuffer(data);
if (sb.length() % 2 != 0)
sb.insert(sb.length(), "0");
//确定长度
int length = sb.length() / 2;
//创建报文体
byte[] msg = new byte[length];
//组装报文
for (int i = 0; i < length; i++) {
String sub = sb.substring(i * 2, i * 2 + 2);
msg[i] = Integer.valueOf(sub, 16).byteValue();
}
return msg;
}
把数据转化为BCD的ascii
/*
* @param data 需要转换的数据
* @ ASCII类型的报文
/
public static byte[] GroupOfASCII(String data) {
return data.getBytes();
}
把数据转化为长度为LLLVar的报文
/*
* @param data 需要转换的数据
* @ type类型 bcd或者ascii
/
public static byte[] GroupOfLLLVar(String data, String Type) {
StringBuffer sb = new StringBuffer();
if (Type.equals(Common.BCD)) {
if (data.length() > 255) {
sb.append("0" + data.length() / 2);
} else {
int[] length = {data.length() / 256, data.length() % 256};
for (int i = 0; i < length.length; i++) {
StringBuffer s = new StringBuffer("0");
String str = length[i] < 10 ? s.append(length[i]).toString()
: String.valueOf(length[i]);
sb.append(str);
}
}
sb.append(data);
return GroupOfBCD(sb.toString());
} else {
return ArrayMerge(GroupOfASCII(data)); //长度算错
}
}
把数据转化为长度为LLVar的报文
/*
* @param data 需要转换的数据
* @ type类型 bcd或者ascii
/
public static byte[] GroupOfLLVar(String data, String Type) {
if (Type.equals(Common.BCD)) {
int length = data.length();
StringBuffer sb = new StringBuffer();
String mStr = length < 10 ? new StringBuffer("0").append(length).toString() : String.valueOf(length);
sb.append(mStr);
sb.append(data);
return GroupOfBCD(sb.toString());
} else {
return ArrayMerge(GroupOfASCII(data));
}
}
把每一域的数组值合并
/*
* @param data 需要合并的数据,可变长度的参数列表
* @ 合并所有转换后的数组,组装报文。
/
public static byte[] ArrayMerge(byte[]... data) {
//计算长度
int length = 0;
for (byte[] t : data) {
length += t.length;
}
//计算报文长度
byte[] mlength = length(length);
//建立目标数组
byte[] result = new byte[length + mlength.length];
//导入报文长度
System.arraycopy(mlength, 0, result, 0, mlength.length);
//设置导入位置
int begin = mlength.length;
for (byte[] t : data) {
System.arraycopy(t, 0, result, begin, t.length);
begin += t.length;
}
return result;
}
传入参数为list集合的数组组装方法
public static byte[] ArrayMerge( List<byte[]> data) {
//计算长度
int length = 0;
for (byte[] t : data) {
length += t.length;
}
//计算报文长度
byte[] mlength = length(length);
//建立目标数组
byte[] result = new byte[length + mlength.length];
//导入报文长度
System.arraycopy(mlength, 0, result, 0, mlength.length);
//设置导入位置
int begin = mlength.length;
for (byte[] t : data) {
System.arraycopy(t, 0, result, begin, t.length);
begin += t.length;
}
return result;
}
组装mac计算需要的数组集合
/*
* @param data 需要合并的数据,可变长度的参数列表
* @ 合并所有转换后的数组,组装报文。
*/
public static byte[] ArrayMerge64(byte[]... data) {
//计算长度
int length = 0;
for (byte[] t : data) {
length += t.length;
}
//建立目标数组
byte[] result = new byte[length];//设置导入位置 int begin = 0; for (byte[] t : data) { System.arraycopy(t, 0, result, begin, t.length); begin += t.length; } return result; } public static byte[] ArrayMerge64(List<byte[]> data) { //计算长度 int length = 0; for (byte[] t : data) { length += t.length; } //建立目标数组 byte[] result = new byte[length]; //设置导入位置 int begin = 0; for (byte[] t : data) { System.arraycopy(t, 0, result, begin, t.length); begin += t.length; } return result; }
计算报文的长度
/**
* @param length 传入报文长度
* @ 计算报文长度转换为byte格式
*/
private static byte[] length(int length) {
byte[] result = new byte[2];
int[] key = {length / 256, length % 256};
for (int i = 0; i < key.length; i++) {
StringBuffer sb = new StringBuffer("0");
String str = key[i] < 10 ? sb.append(key[i]).toString() :
String.valueOf(key[i]);
result[i] = Integer.valueOf(str).byteValue();
}
return result;
}
通过上面的方法我们可以轻松组装一个报文,下面举个例子
LinkedHashMap<String, String> data_consumer = new LinkedHashMap<String, String>();
data_consumer.put("2", cardBean.getCardNo());
data_consumer.put("3", "数据");
data_consumer.put("4", 数据); //金额 55域9f02相等
data_consumer.put("11", 数据);
data_consumer.put("11", "数据");
data_consumer.put("14", "数据");
data_consumer.put("22", "数据");
data_consumer.put("23", 数据);
data_consumer.put("25", "数据");
data_consumer.put("26", "数据");
data_consumer.put("35", 数据);
data_consumer.put("41", 数据);
data_consumer.put("42", 数据);
data_consumer.put("49", "数据")
data_consumer.put("52", "密码加密数据");
data_consumer.put("53", "数据");
data_consumer.put("55", 数据)
data_consumer.put("60", "数据");
String yu64 = BWTools.get64yu(数据)));
data_consumer.put("64", 数据);
byte[] xf_consumer = BWTools.makeBW(data_consumer, Common.Consumer, Common.consume_bitmap);
一个list集合传入对应的数据直接是string就行,会最后通道我们的makeBW方法返回一个byte[]数组可以直接用scoket传给通道,
}
下面讲解通道返回的byte[]如何转换为我们看的懂得数据即解析8583报文
public static LinkedHashMap<String, String> analyzeBW(String bwdata) {
LinkedHashMap<String, String> filedMap = new LinkedHashMap<>();
String bwString = bwdata;
String temp1 = bwString.substring(0, 2);
String temp2 = bwString.substring(2, 4);
int int1 = Integer.parseInt(temp1, 16);
int int2 = Integer.parseInt(temp2, 16);
int data = int1 * 256 + int2;
String bw_result = bwString.substring(4, data * 2 + 4);
Log.d("serve返回的报文不含长度", bw_result);
String bitmap = bw_result.substring(26, 42);
String yu_data = bw_result.substring(42);
String bitMap128Str = hexString2binaryString(bitmap);
for (int i = 1; i < bitMap128Str.length(); i++) {
if (bitMap128Str.charAt(i) == '1') {
String filedValue = "";// 字段值
String filedName = "" + (i + 1);// FIELD005
// 获取域定义信息
String defLen = map2().get(filedName);
boolean isFixLen = true;// 是否是变成
if (defLen.startsWith("LL")) {
isFixLen = false;
}
// 截取域信息
if (!isFixLen) {// 变长域
int defLen1 = 0;
int start_point = 0;
if (defLen.startsWith("LLVAR")) {
defLen = yu_data.substring(0, 2);
defLen1 = Integer.valueOf(defLen);
start_point = 2;
}
if (defLen.startsWith("LLLVAR")) {
defLen = yu_data.substring(0, 4);
int temp = Integer.parseInt(defLen);
String temp3 = defLen.substring(0, 2);
String temp4 = defLen.substring(2, 4);
int int3 = Integer.parseInt(temp3);
int int4 = Integer.parseInt(temp4);
// defLen1 = int3 * 256 + int4;
defLen1 = temp;
start_point = 4;
//报文长度是长度x2,bcd不需要乘以2
}
String type = map1().get(filedName);
if (filedName.equals("55")) {
//此处55域
filedValue = yu_data.substring(start_point, start_point + defLen1 * 2);
yu_data = yu_data.substring(start_point + defLen1 * 2);
} else if (type.startsWith("BCD")) {
filedValue = yu_data.substring(start_point, start_point + defLen1);
if (defLen1 % 2 != 0) {
yu_data = yu_data.substring(start_point + defLen1 + 1);
} else {
yu_data = yu_data.substring(start_point + defLen1);
}
} else {
filedValue = hexTolLetter(yu_data.substring(start_point, start_point + defLen1 * 2));
yu_data = yu_data.substring(start_point + defLen1 * 2);
}
} else {// 不变长域
int defLen2 = Integer.parseInt(map2().get(filedName));
String type2 = map1().get(filedName);
if (filedName.equals("64")) {//64不关心类型 2位变一位 同理ascii
filedValue = hexTolLetter(yu_data.substring(0, defLen2 * 2));
} else if (type2.startsWith("BCD")) {
filedValue = yu_data.substring(0, defLen2);
if (defLen2 % 2 != 0) {
yu_data = yu_data.substring(defLen2 + 1);
} else {
yu_data = yu_data.substring(defLen2);
}
} else {
filedValue = hexTolLetter(yu_data.substring(0, defLen2 * 2));
yu_data = yu_data.substring(defLen2 * 2);
}
}
filedMap.put(filedName, filedValue);
}
}
return filedMap;
}
方法有点吧通告放回的byte[]数组转化为16进制的8583报文string类型作为参数传入即可,其实就是一个循环判断便利报文根据类型解析的方法,这个方法需要确定报文每个域的类型和长度所有需要2个配置集合
private static HashMap<String, String> map1() {
HashMap<String, String> map1 = new HashMap<>();
map1.put("head", "BCD");
map1.put("type", "BCD");
map1.put("map", "BCD");
map1.put("2", "BCD");
map1.put("3", "BCD");
map1.put("4", "BCD");
map1.put("11", "BCD");
map1.put("12", "BCD");
map1.put("13", "BCD");
map1.put("14", "BCD");
map1.put("15", "BCD");
map1.put("22", "BCD");
map1.put("23", "BCD");
map1.put("25", "BCD");
map1.put("26", "BCD");
map1.put("32", "BCD");
map1.put("35", "BCD");
map1.put("36", "BCD");
map1.put("37", "ASCII");
map1.put("38", "ASCII");
map1.put("39", "ASCII");
map1.put("41", "ASCII");
map1.put("42", "ASCII");
map1.put("44", "ASCII");
map1.put("49", "ASCII");
map1.put("52", "BCD");
map1.put("53", "BCD");
map1.put("55", "BCD");
map1.put("54", "ASCII");
map1.put("60", "BCD");
map1.put("61", "BCD");
map1.put("62", "ASCII");
map1.put("63", "ASCII");
map1.put("64", "ASCII"); //判断是binary
return map1;
}
private static HashMap<String, String> map2() {
HashMap<String, String> map2 = new HashMap<>();
map2.put("head", "22");
map2.put("type", "4");
map2.put("map", "16");
map2.put("2", "LLVAR019");
map2.put("3", "6");
map2.put("4", "12");
map2.put("5", "12");
map2.put("6", "12");
map2.put("7", "10");
map2.put("9", "8");
map2.put("10", "8");
map2.put("11", "6");
map2.put("12", "6");
map2.put("13", "4");
map2.put("14", "4");
map2.put("15", "4");
map2.put("16", "4");
map2.put("18", "4");
map2.put("19", "3");
map2.put("22", "3");
map2.put("23", "3");
map2.put("25", "2");
map2.put("26", "2");
map2.put("32", "LLVAR011");
map2.put("33", "LLVAR011");
map2.put("35", "LLVAR037");
map2.put("36", "LLLVAR104");
map2.put("37", "12");
map2.put("38", "6");
map2.put("39", "2");
map2.put("41", "8");
map2.put("42", "15");
map2.put("43", "40");
map2.put("44", "LLVAR25");
map2.put("45", "LLVAR79");
map2.put("48", "LLLVAR512");
map2.put("49", "3");
map2.put("50", "3");
map2.put("51", "3");
map2.put("52", "16"); // 64bit的二进制数
map2.put("53", "16");
map2.put("55", "LLLVAR255");
map2.put("54", "LLLVAR040");
map2.put("60", "LLLVAR100");
map2.put("61", "LLLVAR29");
map2.put("62", "LLLVAR200");
map2.put("63", "LLLVAR63");
map2.put("64", "8");
return map2;
}
map1确定报文的类型map2确定报文的长度,最终返回一个list的集合 key是域的地址,value就是string明文数据
下面贴出几个上面用得方法
/**
* 位图操作
* <p>
* 把16位图的字节数组转化成128位01字符串
*/
private static String get16BitMapStr(byte[] bitMap16) {
String bitMap128 = "";
// 16位图转2进制位图128位字符串
for (int i = 0; i < bitMap16.length; i++) {
int bc = bitMap16[i];
bc = (bc < 0) ? (bc + 256) : bc;
String bitnaryStr = Integer.toBinaryString(bc);// 二进制字符串
// 左补零,保证是8位
String rightBitnaryStr = strCopy("0", Math.abs(8 - bitnaryStr.length())) + bitnaryStr;// 位图二进制字符串
// 先去除多余的零,然后组装128域二进制字符串
bitMap128 += rightBitnaryStr;
}
return bitMap128;
}
/**
* 复制字符
*
* @param str
* @param count
* @return
*/
private static String strCopy(String str, int count) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < count; i++) {
sb.append(str);
}
return sb.toString();
}
// 返回字段号码,例如005
public static String getNumThree(int i) {
String len = "";
String iStr = String.valueOf(i);
len = strCopy("0", 3 - iStr.length()) + iStr;
return len;
}
/**
* 16进制转化为字母
*
* @param hex 要转化的16进制数,用逗号隔开 如:536861646f77
* @return
*/
private static String hexTolLetter(String hex) {
StringBuilder sb = new StringBuilder();
while (hex.length() > 0) {
String temp = hex.substring(0, 2);
int i = Integer.parseInt(temp, 16);
sb.append((char) i);
hex = hex.substring(2);
}
return sb.toString();
}
/**
* ascii
* 字符串中每个字母转化为16进制
*
* @param letter
* @return
*/
public static String letterToH(String letter) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < letter.length(); i++) {
char c = letter.charAt(i);
String s = Integer.toHexString(c);
if (s.length() == 1) {
s = "0" + s;
}
sb.append(s);
System.out.println(s);
}
// sb.deleteCharAt(sb.length() - 2);
return sb.toString();
}
/**
* 16进制转二进制
*
* @param hexString
* @return
*/
public static String hexString2binaryString(String hexString) {
if (hexString == null || hexString.length() % 2 != 0)
return null;
String bString = "", tmp;
for (int i = 0; i < hexString.length(); i++) {
tmp = "0000" + Integer.toBinaryString(Integer.parseInt(hexString.substring(i, i + 1), 16));
bString += tmp.substring(tmp.length() - 4);
}
return bString;
}
/**
* 二进制转16进制
*
* @param bString
* @return
*/
private static String binaryString2hexString(String bString) {
if (bString == null || bString.equals("") || bString.length() % 8 != 0)
return null;
StringBuffer tmp = new StringBuffer();
int iTmp = 0;
for (int i = 0; i < bString.length(); i += 4) {
iTmp = 0;
for (int j = 0; j < 4; j++) {
iTmp += Integer.parseInt(bString.substring(i + j, i + j + 1)) << (4 - j - 1);
}
tmp.append(Integer.toHexString(iTmp));
}
return tmp.toString();
}
下面讲讲密钥的加解密,没有ping和mac是组装不了52和64的
下面讲讲,tmk终端主密钥,主密钥由传输密钥保护,所以需要先拿到传输密钥一般pos的初始化都是拿主密钥,不过初始化可能会分几步,我拿我们平台举例,初始化分3步
初始化——握手(第一步)
62域返回一次性的临时随机数Ans…6(LLLVAR) 6位的随机数RANDOM-NUM。
初始化——获取临时密钥(第二步)
62域
返回一次性的临时密钥TMPKEY(单倍长密钥)的密文ans…12(LLLVAR);前8个字节是密文,后4个字节是checkvalue。
该密钥使用随机数Ans…6(LLLVAR) 6位的随机数RANDOM-NUM后补00,对TMPKEY做DES加密产生。
初始化——获取POS主密钥(第三步)
62域
返回POS主密钥MASTERKEY(双倍长密钥)的密文ans…16(LLLVAR);
该密钥使用主密钥受TMPKEY保护,做DES加密产生。
那我们就根据要去一步一步获取吧
首先先通道scoket拿到random_num和tmpkey
开始解析传输密钥密文下面是代码逻辑
byte[] bytes_handData2 = ParseTMPKEY(rando_num);
byte[] tempkey = new byte[8];//建立一个长度为8的数组放传输密钥密文
System.arraycopy(bytes_handData2, 0, tempkey, 0, 8);
byte[] decrypt_tmpkey = DesUtils.decrypt(tempkey, radom); //临时密钥解密后的数据
byte[] encrypt = DesUtils.encrypt(ParseTMPKEY("0000000000000000"),decrypt_tmpkey);//密钥 明文都是16进制的,此次的16个0为16进制
String check1 = BWTools.byte2Hex(encrypt).substring(0, 8);//传输密钥明文对16个0做des加密的前8位和checkvalue对比一样说checkvalue校验正确
String check2= handData2.substring(16, 24);
LogUtils.i("初始化第二步校验checkvalue——1,计算得到",check1);
LogUtils.i("初始化第二步校验checkvalue——2==16进制",check2);
int i = check1.compareToIgnoreCase(check2);
if (i == 0) {
Utils.runOnUIThread(new Runnable() {
@Override
public void run() {
ToastUtil.showToast("checkvalue检查正确");
}
});
}
下面解密主密钥和校验checkvalue
final byte[] masterkey = ParseTMPKEY(主密钥密文);
byte[] masterkey1 = new byte[8];
byte[] masterkey2 = new byte[8];
System.arraycopy(masterkey, 0, masterkey1, 0, 8);
System.arraycopy(masterkey, 8, masterkey2, 0, 8);
byte[] decrypt_masterkey1 = DesUtils.decrypt(masterkey1, decrypt_tmpkey);
byte[] decrypt_masterkey2 = DesUtils.decrypt(masterkey2, decrypt_tmpkey);
byte[] result_master = new byte[16];
System.arraycopy(decrypt_masterkey1, 0, result_master, 0, 8);
System.arraycopy(decrypt_masterkey2, 0, result_master, 8, result_master.length - 8);
String master_key = byte2Hex(result_master);//获得终端主密钥的明文
就是根据文档做加解密
下面是签到交易获取工作密钥的密文
String s = BWTools.analyzeBW(byte2Hex(d)).get("62");//获取工作密钥密文
String result = BWTools.letterToH(s);//密文转化为ascii转16进制
LogUtils.d("签到原始", result);
final int resultlength = result.length();
LogUtils.d("goRegister", "resultlength:" + resultlength);
if (resultlength < 72) {
Utils.runOnUIThread(new Runnable() {
@Override
public void run() {
ToastUtil.showToast("签到62域长度问题,长度为" + resultlength);
}
});
return;
}
String ping = result.substring(0, 32);
String ping_checkvalue = result.substring(32, 40);
Log.d("ping", ping);
String mac = result.substring(40, 72);
String mac_checkvalue = result.substring(72, 80);
byte[] bytes_ping = DesUtils.ThreeDecrypt(result_master_bytes, ParseTMPKEY(ping));
byte[] bytes_mac = DesUtils.ThreeDecrypt(result_master_bytes, ParseTMPKEY(mac));
String ping_des = zm(bytes_ping, "ping密钥解密后");
String mac_des = zm(bytes_mac, "mac密钥解密后");
String ping_checkvalue_js = (BWTools.byte2Hex(DesUtils.ThreeEncryptt(ParseTMPKEY(ping_des),ParseTMPKEY("00000000000000000000000000000000")))).substring(0,8);
LogUtils.d("ping_checkvalue1",ping_checkvalue);
LogUtils.d("ping_checkvalue2",ping_checkvalue_js);
String mac_checkvalue_js = (BWTools.byte2Hex(DesUtils.encrypt(ParseTMPKEY("0000000000000000"),ParseTMPKEY(mac_des)))).substring(0,8);
LogUtils.d("mac_checkvalue1",mac_checkvalue);
LogUtils.d("mac_checkvalue2",mac_checkvalue_js);
int i = ping_checkvalue.compareToIgnoreCase(ping_checkvalue_js);
int i1 = mac_checkvalue.compareToIgnoreCase(mac_checkvalue_js);
if (i == 0 && i1 == 0) {
Utils.runOnUIThread(new Runnable() {
@Override
public void run() {
ToastUtil.showToast("工作密钥checkvalue检测正确");
}
});
}
下面贴出部分方法
/**
* ascii
* 字符串中每个字母转化为16进制
*
* @param letter
* @return
*/
public static String letterToH(String letter) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < letter.length(); i++) {
char c = letter.charAt(i);
String s = Integer.toHexString(c);
if (s.length() == 1) {
s = "0" + s;
}
sb.append(s);
System.out.println(s);
}
// sb.deleteCharAt(sb.length() - 2);
return sb.toString();
}
到此就获得了ping和mac明文下面可以愉快的加密52和64啦
先讲52,52域很简单 首先需要银行卡号,我的方法是直接传入35域数据,根据自己需要可以改为直接传银行卡
银行卡从倒数第2位先前截取13个前面不0000和("06"+密码+"FFFFFFFF")做异或,异或的结果用ping密文做3des加密下面是方法
/**
* 52yu获取
* @param yu35data
* @param ping_des
* @param pwd
* @return
/
public static String get52yu(String yu35data, String ping_des, String pwd) {
String[] ds = yu35data.split("D");
String s1 = "0000"+ds[0].substring(ds[0].length()-13,ds[0].length()-1);
String s2 = "06"+pwd+"FFFFFFFF";
String xor = Tools.xor(s1, s2);
Log.d("Tools52_s1", s1);
Log.d("Tools52_s2", s2);
Log.d("Tools52_xor", xor);
byte[] threeEncryptt = DesUtils.ThreeEncryptt(ParseTMPKEY(ping_des),ParseTMPKEY(xor+"0000000000000000"));
Log.d("Tools3des加密后的数值", byte2Hex(threeEncryptt).substring(0, 16));
return byte2Hex(threeEncryptt).substring(0,16);
}
下面讲解64域mac的值计算方法
mac比较复杂,用伪代码解释下
将type_63域的报文数据(16进制数据)分成8位1组,前后异或下去等到的结果是16位ascii转换32位(16进制字符)
加入前16位a1后后16位a2,a1和mac密钥做des加密(或者3des看通道要求)得到的结果 b1,然后b1和a2做异或运算得到c,最后c和mac做des加密或者3des加密,结果就是mac
下面是方法
/*
* 获取64域mac
* @param bw type-63域报文
* @param mac mac密钥
* @return
/
public static String getMac(String bw, String mac) {
// type-63域报文先分成16个一组前后异或下去
String xor_result = Tools.xor_result(bw);
//将 报文每8位结果异或结果的16位转32位(将ASC字符串转16进制字符)
String xor_result_2_hex = Tools.xor_result_2_hex(xor_result);
//System.out.println(xor_result_2_hex);
String result1 = xor_result_2_hex.substring(0, 16);
String result2 = xor_result_2_hex.substring(16);
//System.out.println(result1);
// System.out.println(result2);
//byte[] bytes = DesUtils.ThreeEncryptt(ParseUtils.ParseTMPKEY(mac), ParseUtils.ParseTMPKEY(result1 + "0000000000000000")); 3des
byte[] bytes = DesUtils.encrypt(ParseTMPKEY(result1 + "0000000000000000"), ParseTMPKEY(mac.substring(0,16)));
String s = byte2Hex(bytes);
String substring = s.substring(0, 16);
// System.out.println(substring);
String xor = Tools.xor(substring, result2);
// System.out.println(xor);
//byte[] bytes2 = DesUtils.ThreeEncryptt(ParseUtils.ParseTMPKEY(mac), ParseUtils.ParseTMPKEY(xor + "0000000000000000")); 3des
byte[] bytes2 = DesUtils.encrypt(ParseTMPKEY(xor + "0000000000000000"), ParseTMPKEY(mac.substring(0,16)));
String s2 = byte2Hex(bytes2);
Log.d("mac__64为截取", s2);
return s2.substring(0, 8);
}
下面贴出部分方法
/*将报文每8位结果异或结果的16转32
* 将ASC字符串转16进制字符
*
* @param asc
* @return
*/
private static String xor_result_2_hex(String asc) {
StringBuffer hex = new StringBuffer();
try {
byte[] bs = asc.toUpperCase().getBytes("UTF-8");
for (byte b : bs) {
hex.append(Integer.toHexString(new Byte(b).intValue()));
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return hex.toString();
}
//报文异或结果
private static String xor_result(String bw) {
List<String> bw_sub = sub(bw);
//System.out.println(bw_sub.toString());
String temp = bw_sub.get(0);
for (int i = 1; i < bw_sub.size(); i++) {
temp = xor(temp, bw_sub.get(i));
//System.out.println("第"+i+"次"+temp);
}
return temp;
}
//string截取为16位一组的数据 不足补0
private static List<String> sub(String bw) {
List<String> listdata = new ArrayList();
while (bw.length() > 16) {
String substring = bw.substring(0, 16);
listdata.add(substring);
bw = bw.substring(16);
}
if (bw.length() == 16
) {
listdata.add(bw);
}
if (bw.length() < 16) {
int i = 16 - bw.length();
for (int i1 = 0; i1 < i; i1++) {
bw = bw + "0";
}
listdata.add(bw);
}
return listdata;
}
//2个16进制的string异或
public static String xor(String string, String string2) {
int[] i = toInterger(string);
int[] j = toInterger(string2);
int length = i.length == j.length ? i.length : 0;
int[] result = new int[length];
StringBuffer sb = new StringBuffer();
for (int k = 0; k < result.length; k++) {
int data = i[k] ^ j[k];
sb.append(Integer.toHexString(data));
}
return sb.toString().toUpperCase();
}
//16进制string转int
private static int[] toInterger(String string) {
int length = string.length();
int[] result = new int[length];
for (int i = 0; i < length; i++) {
String str = string.substring(i, i + 1);
int k = Integer.valueOf(str, 16);
result[i] = k;
}
return result;
}
public static String getBankCardNum(String cardNum) {
StringBuilder sb = new StringBuilder();
sb.append(cardNum.substring(0, 6));
sb.append("*******");
sb.append(cardNum.substring(cardNum.length() - 4));
return sb.toString();
}
最后贴出我的64域获取方法,可根据需求自定义修改
/**
* 获取64mac
*
* @param data type-63域
* @param mac_des
* @return
*/
public static String get64yu(LinkedHashMap<String, String> data, String type, String bitmap, String mac_des) {
List<byte[]> result = new ArrayList<>();
//tpdu+MSGHEAD +type+bitmap
//Common.Messagehead=tpdu+MSGHEAD
String msghead = type + PartsUtils.GroupOfBitMap(bitmap);
byte[] bytes1 = PartsUtils.GroupOfBCD(msghead);
Log.d("BWTools", "bytes1:" + Arrays.toString(bytes1));
result.add(bytes1);
for (Map.Entry<String, String> entry : data.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
String yuType = map1().get(key);
String yuLength = map2().get(key);
byte[] bytes = null;
if (yuLength.startsWith("LLVAR")) {//变长
if (yuType.startsWith("BCD")) {
bytes = PartsUtils.GroupOfLLVar(value, Common.BCD);
} else {
bytes = PartsUtils.GroupOfLLVar(value, Common.ASCII);
}
} else if (yuLength.startsWith("LLLVAR")) {
if (yuType.startsWith("BCD")) {
bytes = PartsUtils.GroupOfLLLVar(value, Common.BCD);
} else {
bytes = PartsUtils.GroupOfLLLVar(value, Common.ASCII);
}
} else {//定长
if (yuType.startsWith("BCD")) {
bytes = PartsUtils.GroupOfBCD(value);
} else {
bytes = PartsUtils.GroupOfASCII(value);
}
}
result.add(bytes);
}
byte[] bw_result = PartsUtils.ArrayMerge64(result);
String s = byte2Hex(bw_result);
String mac = Tools.getMac(s, mac_des);
return mac;
}
任然不明白的,可以留言!大家一起进步