针对银联生成ARQC,ARPC,还有MAC进行了软加密实现,一般的银行都是进行调用加密机实现,为了方便测试使用自己对其进行了软加密算法实现:
基本帮助类如下:
package com.omini.common.utils;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* @author sandy
* @version $Revision: 1.1 $ 建立日期 2012-9-11
*/
public class StringUtils
{
/**
* 抽取字符或是数字 若isNumber传入true则表示抽取数字,false则表示抽取字符
*
* @param result
* @param isNumber
* @return
*/
public static String extract(String result, boolean isNumber)
{
if (null == result || result.equals("") || result.length() == 0)
{
throw new IllegalArgumentException("参数不正确,不能为空!");
}
StringBuffer resultBuffer = new StringBuffer();
char[] chars = result.toUpperCase().toCharArray();
for (char c : chars)
{
boolean flag = Character.isDigit(c);
if (isNumber && flag)
{
resultBuffer.append(c);
}
if (!flag && !isNumber)
{
resultBuffer.append(c);
}
}
return resultBuffer.toString();
}
/**
* 减去10
*
* @param input
* @return
*/
public static String divide(String input)
{
if (null == input || input.equals("") || input.length() == 0)
{
throw new IllegalArgumentException("参数不正确,不能为空!");
}
char[] output = new char[input.length()];
for (int i = 0; i < input.length(); i++)
{
if (output[i] > 96)
{
output[i] = (char) (output[i] - 49);
} else if (output[i] > 64)
{
output[i] = (char) (output[i] - 17);
} else
{
output[i] = output[i];
}
}
return Arrays.toString(output);
}
/**
* 追加字符到指定长度的字符
*
* @param srcData
* :原数据
* @param alignMode
* :对齐方式
* @param paddCharacter
* :填补的字符
* @param totalLen
* :填充到的长度
* @return
*/
public static String padding(String srcData, String alignMode, String paddCharacter, int totalLen)
{
if (srcData == null || null == alignMode || null == paddCharacter || totalLen == 0)
{
throw new IllegalArgumentException("传入的数据不能为空或0,请检查数据!");
}
int paddLen = totalLen - srcData.length();
StringBuffer paddResultBuffer = new StringBuffer();
if (alignMode.equalsIgnoreCase("left"))
{
for (int i = 0; i < paddLen; i++)
{
paddResultBuffer.append(paddCharacter);
}
paddResultBuffer.append(srcData);
} else if (alignMode.equalsIgnoreCase("right"))
{
paddResultBuffer.append(srcData);
for (int i = 0; i < paddLen; i++)
{
paddResultBuffer.append(paddCharacter);
}
} else
{
throw new IllegalArgumentException("paddAlign is not left or right,please check !");
}
return paddResultBuffer.toString();
}
/**
* 两个数据进行异或操作
*
* @param hexSrcData1
* :32CB95B36D89477C
* @param hexSrcData2
* :3030000000000000
* @return
*/
public static String XOR(String hexSrcData1, String hexSrcData2)
{
if (hexSrcData1.length() != hexSrcData2.length())
{
throw new IllegalArgumentException("异或的两个数据长度不相等,请检查数据!");
}
byte[] bytes1 = HexBinary.decode(hexSrcData1);
byte[] bytes2 = HexBinary.decode(hexSrcData2);
ByteBuffer buffer = ByteBuffer.allocate(bytes2.length);
for (int i = 0; i < bytes2.length; i++)
{
byte temp = (byte) ((int) bytes1[i] ^ (int) bytes2[i]);
buffer.put(temp);
}
return HexBinary.encode(buffer.array());
}
/**
* 按位取反操作
*
* @param hexSrcData
* @return
*/
public static String reversBytes(String hexSrcData)
{
if (null == hexSrcData || hexSrcData.equals("") || hexSrcData.length() == 0)
{
throw new IllegalArgumentException("非法的按位取反的数据,请检查数据");
}
byte[] srcBytes = HexBinary.decode(hexSrcData);
ByteBuffer destBuffer = ByteBuffer.allocate(srcBytes.length);
for (int i = 0; i < srcBytes.length; i++)
{
byte temp = (byte) (~(int) srcBytes[i]);
destBuffer.put(temp);
}
return HexBinary.encode(destBuffer.array());
}
}
package com.omini.common.utils;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* @author sandy
* @version $Revision: 1.1 $ 建立日期 2012-9-11
*/
public class DESUtils
{
/**
* DES加密数据非填充方式
*
* @param hexKey
* @param hexData
* @param mode
* @return
* @throws Exception
*/
public static String decEncNoPaddingDES(String hexKey, String hexData, int mode) throws Exception
{
SecretKey desKey = new SecretKeySpec(HexBinary.decode(hexKey), "DES/ECB/NoPadding");
Cipher cp = Cipher.getInstance("DES/ECB/NoPadding");
cp.init(mode, desKey);
byte[] bytes = cp.doFinal(HexBinary.decode(hexData));
return HexBinary.encode(bytes);
}
public static String encrypt(String hexKey, String hexData) throws Exception
{
SecretKey desKey = new SecretKeySpec(HexBinary.decode(hexKey), "DES");
Cipher cp = Cipher.getInstance("DES");
cp.init(Cipher.ENCRYPT_MODE, desKey);
byte[] bytes = cp.doFinal(HexBinary.decode(hexData));
return HexBinary.encode(bytes);
}
/**
* 3Des加密非填充
*
* @param hexKey
* @param hexData
* @return
* @throws Exception
*/
public static String encryptDesSede(String hexKey, String hexData) throws Exception
{
SecretKey desKey = new SecretKeySpec(HexBinary.decode(hexKey), "DESede/ECB/NoPadding");
Cipher cp = Cipher.getInstance("DESede/ECB/NoPadding");
cp.init(Cipher.ENCRYPT_MODE, desKey);
byte[] bytes = cp.doFinal(HexBinary.decode(hexData));
return HexBinary.encode(bytes);
}
}
package com.omini.common.utils;
/**
* @author sandy
* @version $Revision: 1.1 $ 建立日期 2012-9-11
*/
public class HexBinary
{
/**
* Creates a clone of the given byte array.
*/
public static byte[] getClone(byte[] pHexBinary)
{
byte[] result = new byte[pHexBinary.length];
System.arraycopy(pHexBinary, 0, result, 0, pHexBinary.length);
return result;
}
/**
* Converts the string <code>pValue</code> into an array of hex bytes.
*/
public static byte[] decode(String pValue)
{
if ((pValue.length() % 2) != 0)
{
throw new IllegalArgumentException("A HexBinary string must have even length.");
}
byte[] result = new byte[pValue.length() / 2];
int j = 0;
for (int i = 0; i < pValue.length();)
{
byte b;
char c = pValue.charAt(i++);
char d = pValue.charAt(i++);
if (c >= '0' && c <= '9')
{
b = (byte) ((c - '0') << 4);
} else if (c >= 'A' && c <= 'F')
{
b = (byte) ((c - 'A' + 10) << 4);
} else if (c >= 'a' && c <= 'f')
{
b = (byte) ((c - 'a' + 10) << 4);
} else
{
throw new IllegalArgumentException("Invalid hex digit: " + c);
}
if (d >= '0' && d <= '9')
{
b += (byte) (d - '0');
} else if (d >= 'A' && d <= 'F')
{
b += (byte) (d - 'A' + 10);
} else if (d >= 'a' && d <= 'f')
{
b += (byte) (d - 'a' + 10);
} else
{
throw new IllegalArgumentException("Invalid hex digit: " + d);
}
result[j++] = b;
}
return result;
}
/**
* Converts the byte array <code>pHexBinary</code> into a string.
*/
public static String encode(byte[] pHexBinary)
{
StringBuffer result = new StringBuffer();
for (int i = 0; i < pHexBinary.length; i++)
{
byte b = pHexBinary[i];
byte c = (byte) ((b & 0xf0) >> 4);
if (c <= 9)
{
result.append((char) ('0' + c));
} else
{
result.append((char) ('A' + c - 10));
}
c = (byte) (b & 0x0f);
if (c <= 9)
{
result.append((char) ('0' + c));
} else
{
result.append((char) ('A' + c - 10));
}
}
return result.toString();
}
public static void main(String[] args)
{
String a = "12";
System.out.println(encode(a.getBytes()));
String ab = "00000000000100000000000001560000000000015611041501112233447C00000603A00002";
System.out.println("::::::::::[" + HexBinary.encode(ab.getBytes()) + "]");
}
}
package com.omini.common.utils;
import javax.crypto.Cipher;
/**
* 生成ARQC,ARPC,MAC,CVN,CVN2工具类
*
* @author sandy
* @version $Revision: 1.1 $ 建立日期 2012-9-11
*/
public class UnionUtils
{
/**
* 其实生成ARQC就是调用的生成MAC方法,只不过MAC值是取得的前4个字节的值
*
* @param pan
* @param panSN
* @param hexATC
* @param mainKey
* @param arqcDataSource
* @return
* @throws Exception
*/
public static String generateARQC(String pan, String panSN, String hexATC, String mainKey, String arqcDataSource) throws Exception
{
if (null == mainKey || mainKey.length() != 32)
{
throw new IllegalArgumentException("非法的工作主密钥的值");
}
if (pan == null || pan.equals("") || pan.length() == 0)
{
throw new IllegalArgumentException("卡号不能为空,请检查传入的卡号");
}
String processKey = generateProcesKey(pan, panSN, hexATC, mainKey);
String result = process(processKey, arqcDataSource);
if (result.length() != 16)
{
throw new IllegalArgumentException("返回的mac结果非8字节(16位hex)");
}
return result;
}
/**
* 根据ARQC来生成ARPC
*
* @param hexArqc表示ARQC的值8字节
* @param pan
* :表示卡号
* @param panSN
* :表示卡序号00或01
* @param hexATC
* :交易记数器
* @param mainKey
* :工作主密钥
* @param authCode
* :授权响应码2个字节
* @return
* @throws Exception
*/
public static String generateARPC(String hexArqc, String pan, String panSN, String hexATC, String mainKey, String authCode) throws Exception
{
if (null == mainKey || mainKey.length() != 32)
{
throw new IllegalArgumentException("非法的工作主密钥的值");
}
if (pan == null || pan.equals("") || pan.length() == 0)
{
throw new IllegalArgumentException("卡号不能为空,请检查传入的卡号");
}
if (hexArqc == null || hexArqc.equals("") || pan.length() == 0 || hexArqc.length() != 16)
{
throw new IllegalArgumentException("非法的ARQC数据");
}
String processKey = generateProcesKey(pan, panSN, hexATC, mainKey);
String paddARC = StringUtils.padding(authCode, "right", "0", 16);
String arqcAndARCXORResult = StringUtils.XOR(hexArqc, paddARC);
String arpc = DESUtils.encryptDesSede(processKey, arqcAndARCXORResult);
System.out.println(arpc);
return arpc;
}
/**
* 生成MAC,并获取计算后的前4个字节 命令中需要加密的数据加密以后再计算MAC。MAC使用对称密钥算法计算的, 步骤如下: 步骤1:初始值为8
* 字节全零(此步骤可省略); 步骤2:下列数据按顺序排列得到一个数据块D: ——CLA、INS、P1、P2 和Lc(Lc 的长度包括MAC
* 的长度); ——ATC(对于发卡行脚本处理,此ATC 在请求中报文中上送);
* ——应用密文(对于发卡行脚本处理,此应用密文通常是ARQC,或AAC,在请求报文中上送); ——命令数据域中的明文或密文数据(如果存在)。
* 步骤3:将上述数据块D 分成8 字节长的数据块D1、D2、D3…最后一块数据块的字节长度为1 到8; 步骤4:如果最后一块数据块的长度为8
* 字节,后面补8 字节数据块:80 00 00 00 00 00 00 00, 执行步骤5; 如果最后一块数据块的长度小于8
* 字节,后面补一个字节80,如果长度到8 字节,执行 步骤5。如果仍然不够8 字节,补00 直到8 字节; 步骤5:用MAC
* 过程密钥对数据块进行加密。MAC 过程密钥的生成见C.4; 图C.1 是使用MAC 过程密钥A 和B 生成MAC 的流程图。 步骤6:MAC
* 的计算结果为8 字节,从最左边的字节开始取4 字节
*
* @param pan
* @param panSN
* @param hexATC
* @param mainKey
* @param macDataSource
* @return
* @throws Exception
*/
public static String generateMAC(String pan, String panSN, String hexATC, String mainKey, String macDataSource) throws Exception
{
if (null == mainKey || mainKey.length() != 32)
{
throw new IllegalArgumentException("非法的工作主密钥的值");
}
if (pan == null || pan.equals("") || pan.length() == 0)
{
throw new IllegalArgumentException("卡号不能为空,请检查传入的卡号");
}
String processKey = generateProcesKey(pan, panSN, hexATC, mainKey);
String result = process(processKey, macDataSource);
if (result.length() != 16)
{
throw new IllegalArgumentException("返回的mac结果非8字节(16位hex)");
}
result = result.substring(0, 8);
return result;
}
/**
* 计算CVN时使用二个64位的验证密钥,KeyA和KeyB。 a) 计算CVN 的数据源包括:
* 主账号(PAN)、卡失效期和服务代码,从左至右顺序编排。 例如19位PAN、4位卡失效期和3位服务代码组成26个字符CVN数据源。 b)
* 将上述数据源扩展成128 位二进制数据(不足128 位右补二进制0)。 c) 将128 位二进制数据分成两个64 位的数据块。最左边的64
* 位为Block1,最右边的64 位为 Block2。 d) 使用KeyA 对Block1 进行加密。 e) 将Block1
* 的加密结果与Block2 进行异或。使用KeyA 对异或结果进行加密。 f) 使用KeyB 对加密结果进行解密。 g) 使用KeyA
* 对解密结果进行加密。 h) 从左至右将加密结果中的数字(0-9)抽出,组成一组数字。 i) 从左至右将加密结果中的字符(A-F)抽出,减10
* 后将余数组成一组数字,排列在步骤(8) 的数字之后。 j) 步骤(9)的左边第一组三位数即为CVN 值
*
* @param pan
* @param invalidDate
* @param serviceCode
* @param hexKey:验证密钥
* @return
* @throws Exception
*/
public static String generateCVN(String pan, String invalidDate, String serviceCode, String hexKey) throws Exception
{
if (null == pan || null == invalidDate || null == serviceCode || null == hexKey)
{
throw new IllegalArgumentException("卡号或是失效日期或服务代码或验证密钥为空!");
}
if (hexKey.length() != 32)
{
throw new IllegalArgumentException("验证密钥长度非32位!");
}
String keyA = hexKey.substring(0, 16);
String keyB = hexKey.substring(16);
StringBuffer cvnDataSource = new StringBuffer();
cvnDataSource.append(pan).append(invalidDate).append(serviceCode);
String cvnDS = StringUtils.padding(cvnDataSource.toString(), "right", "0", 32);
String blockA = cvnDS.substring(0, 16);
String blockB = cvnDS.substring(16);
String keyAEncryptBlockAResult = DESUtils.decEncNoPaddingDES(keyA, blockA, Cipher.ENCRYPT_MODE);
String xorBlockBResult = StringUtils.XOR(keyAEncryptBlockAResult, blockB);
String result = DESUtils.decEncNoPaddingDES(keyA, xorBlockBResult, Cipher.ENCRYPT_MODE);
result = DESUtils.decEncNoPaddingDES(keyB, result, Cipher.DECRYPT_MODE);
result = DESUtils.decEncNoPaddingDES(keyA, result, Cipher.ENCRYPT_MODE);
String numberData = StringUtils.extract(result, true);
String characterData = StringUtils.extract(result, false);
characterData = StringUtils.divide(characterData);
result = numberData + characterData;
if (result.length() < 3)
{
throw new IllegalArgumentException("计算CVN返回的长度小于3位长度不正确");
}
result = result.substring(0, 3);
return result;
}
/**
* 印刷在签名条的右上方处并放在卡号(后4位)后
*
* 生成CVN2其实就是把服务码变成常数000即可
*
* @param pan:卡号
* @param invalidDate:失效日期
* @param serviceCode:服务码
* @param hexKey:验证密钥
* @return
* @throws Exception
*/
public static String generateCVN2(String pan, String invalidDate, String serviceCode, String hexKey) throws Exception
{
return generateCVN(pan,invalidDate,"000",hexKey);
}
/**
* 生成过程密钥
*
* @param pan
* @param panSN
* @param hexATC
* @param mainKey
* @return
* @throws Exception
*/
private static String generateProcesKey(String pan, String panSN, String hexATC, String mainKey) throws Exception
{
int cardNoLength = pan.length();
String cardNoRight14 = pan.substring(cardNoLength - 14);
// 分散因子
String dispersionFactor = cardNoRight14 + panSN;
// 对分散因子取反
String reversDispersionFactor = StringUtils.reversBytes(dispersionFactor);
StringBuffer dispersionBuffer = new StringBuffer();
dispersionBuffer.append(dispersionFactor).append(reversDispersionFactor);
// 生成子密钥
String subKey = DESUtils.encryptDesSede(mainKey, dispersionBuffer.toString());
String paddATC = StringUtils.padding(hexATC, "left", "0", 16);
String reversATC = StringUtils.reversBytes(hexATC);
String paddReversATC = StringUtils.padding(reversATC, "left", "0", 16);
String mergerATC = paddATC + paddReversATC;
// 生成过程密钥
String processKey = DESUtils.encryptDesSede(subKey, mergerATC);
return processKey;
}
/**
* 计算MAC处理
*
* @param processKey
* :过程密钥
* @param macDataSource
* :计算MAC的数据源
* @return
* @throws Exception
*/
private static String process(String processKey, String macDataSource) throws Exception
{
if (null == processKey || processKey.equals("") || processKey.length() != 32)
{
throw new IllegalArgumentException("过程密钥不能为空或不够32位!");
}
String leftKey = processKey.substring(0, 16);
String rightKey = processKey.substring(16);
// 拆分MAC数据源,每组16位hex(8 byte())
String[] ds = splitData(macDataSource);
String des = "";
for (int i = 0; i < ds.length; i++)
{
if (i == 0)
{
// 第一次只做DES加密
des = DESUtils.decEncNoPaddingDES(leftKey, ds[i], Cipher.ENCRYPT_MODE).toUpperCase();
} else
{
// 用上一次 DES加密结果对 第 i 组数据做异或
des = StringUtils.XOR(des, ds[i]);
// 对异或后的数据做DES加密
des = DESUtils.decEncNoPaddingDES(leftKey, des, Cipher.ENCRYPT_MODE).toUpperCase();
}
}
// DES 加密最终结果用processKey后16位解密
des = DESUtils.decEncNoPaddingDES(rightKey, des, Cipher.DECRYPT_MODE).toUpperCase();
// 解密后 再用processKey前16位加密
des = DESUtils.decEncNoPaddingDES(leftKey, des, Cipher.ENCRYPT_MODE).toUpperCase();
return des;
}
/*
* 将hexMacDataSource进行分组 每 16 字符 8byte 一组
*/
private static String[] splitData(String hexMacDataSource)
{
int len = 0;
int modValue = hexMacDataSource.length() % 16;
if (modValue == 0)
{
// 补上80000000000000
hexMacDataSource += "80000000000000";
len = hexMacDataSource.length() / 16;
} else if (modValue == 14)
{
// 补上80
hexMacDataSource += "80";
len = hexMacDataSource.length() / 16;
} else
{
hexMacDataSource += "80";
int hexSrcDataLen = hexMacDataSource.length();
int totalLen = hexSrcDataLen + (16 - modValue - 2);
hexMacDataSource = StringUtils.padding(hexMacDataSource, "right", "0", totalLen);
len = hexMacDataSource.length() / 16;
}
String[] ds = new String[len];
for (int i = 0; i < ds.length; i++)
{
if (hexMacDataSource.length() >= 16)
{
ds[i] = hexMacDataSource.substring(0, 16);
hexMacDataSource = hexMacDataSource.substring(16);
} else
{
throw new IllegalArgumentException("填充的数据非法!");
}
}
return ds;
}
}
测试代码如下:
package com.omini.common.utils;
/**
* @author sandy
* @version $Revision: 1.1 $ 建立日期 2012-9-11
*/
public class UnionUtilsTest
{
public static void main(String[] args) throws Exception
{
// UnionUtilsTest.generateMACTest();
UnionUtilsTest.generateCVN();
}
public static void generateARPCTest() throws Exception
{
String arpc = UnionUtils.generateARPC("32CB95B36D89477C", "6214618888000002074", "00", "0008", "00000000000000000000000000000000", "3030");
System.out.println("arpc=" + arpc);
}
public static void generateMACTest() throws Exception
{
String result = UnionUtils.generateMAC("6214618888000002074", "00", "0029", "00000000000000000000000000000000", "04DA9F790A00299E99DA1521DAA0A3000000000000");
System.out.println("MAC=" + result);
}
public static void generateARQCTest() throws Exception
{
String result = UnionUtils.generateARQC("6214610200000004163", "01", "000B", "77777777777777777777777777777777", "00000001000000000000000001568008000800015610052801112233447C00000B03A03000");
System.out.println("ARQC=" + result);
}
public static void generateCVN() throws Exception
{
String result = UnionUtils.generateCVN("6221234567891234", "0712", "111", "0123456789ABCDEFFEDCBA9876543210");
System.out.println("CVN=" + result);
}
public static void generateCVN2() throws Exception
{
String result = UnionUtils.generateCVN("6221234567891234", "0712", "000", "0123456789ABCDEFFEDCBA9876543210");
System.out.println("CVN2=" + result);
}
}
本文介绍了如何通过软件加密技术实现银联标准的ARQC、ARPC以及MAC算法的生成,包括字符数字抽取、字符串填充、异或运算等功能,以及对过程密钥和MAC数据源的处理。
2213

被折叠的 条评论
为什么被折叠?



