pos 8583报文我的解决方案

最近公司在做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;
}

任然不明白的,可以留言!大家一起进步

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,524评论 18 399
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,823评论 6 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,552评论 18 139
  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,715评论 0 33
  • 【孕妈妈安全出游其他注意事项】孕妇旅行必须要有人陪同。孕妈妈在旅游观光时不要逞强,也不要非看到某些景点不可,如果觉...
    学徒晓成阅读 435评论 0 3