概念理解
mac算法是(Message Authentication Codes 消息认证码算法),是含有密钥散列函数算法。主要通过异或运算,再配合其他加密算法实现mac值的运算,用于校验。
Demo项目下载地址 :https://github.com/Cats-eat-fish/-sm4-MAC-
实现过程
-
将需要加密计算的字符串转换为16进制字符串
例如:
密钥:"12345678901234567890123456789012"
待加密数据(字符串形式):
6000112018111411003020170930101010120171025000755000000005553转化为16进制:
36303030 31313230 31383131 31343131 30303330 32303137 30393330 31303130 31303132 30313731 30323530 30303735 35303030 30303030 30353535 33 -
将转换过的16字符串进行补位分组
部位后的数据(部位规则:字符长度%32!=0补位)
36303030 31313230 31383131 31343131 30303330 32303137 30393330 31303130 31303132 30313731 30323530 30303735 35303030 30303030 30353535 33000000分组后的数据
第 0组明文:36303030 31313230 31383131 31343131
第 1组明文:30303330 32303137 30393330 31303130
第 2组明文:31303132 30313731 30323530 30303735
第 3组明文:35303030 30303030 30353535 33000000 -
将分组过的字符串数组逐步进行异或运算
异或流程
第 0组明文: 36303030 31313230 31383131 31343131
第 1组明文: 30303330 32303137 30393330 31303130
第 1组异或结果:06000300 03010307 01010201 00040001
第 2组明文:31303132 30313731 30323530 30303735
第 2组异或结果:37303232 33303436 31333731 30343734
第3组明文:35303030 30303030 30353535 33000000
与第2组密文异或:02000202 03000406 01060204 03343734 -
将最后异或结果转化为16进制字符串
转换为16进制
30323030 30323032 30333030 30343036 30313036 30323034 30333334 33373334 -
取前16位和后16位,前16位进行sm4加密,加密结果与后16位异或后再加密
前16位
30323030 30323032 30333030 30343036后16位
30313036 30323034 30333334 33373334前16位加密结果
B038A2B2 F3FFE3C8 AC60B377 C70C2DB1与后16位异或结果
80099284 C3CDD3FC 9C538043 F43B1E85再加密结果
1E80F373 40B0FEE6 2C81F356 AFB20BF1 -
取前16个字符作为mac值
mac值:1E80F37340B0FEE6
具体实现过程
准备工作
-
数据转换工具类 TransferUtils.java
- 字符串转换为16进制字符串
public static String StringToHexString(String str) { String data = bytesToHexString(str.getBytes(), 32); return data; }
- 字节数组转换为16进制字符串
/** * 字节数组转化为十六进制字符串 并按照len的倍数进行补"0" * @param bytes 字节数据 * @param len 部位原则 不够len倍数补"0" * @return */ public static String bytesToHexString(byte[] bytes, int len) { StringBuffer hexStr = new StringBuffer(); for (byte b : bytes) { hexStr.append(String.format("%02x", new Integer(b & 0xff))); } //长度不满32的整数倍 在后边添加 "0" while (hexStr.length() % len != 0) { hexStr.append("0"); } return hexStr.toString(); }
- 将字符串str 按照长度len 进行分组
/** * 将字符串str 按照长度len 进行分组 * @param str 字符串 * @param len 每组字符长度 * @return */ public static String[] dataGrouping(String str, int len) { int lenth = str.length() % len == 0 ? str.length() / len : str.length() / len + 1; String[] data = new String[lenth]; for (int i = 0; i < lenth; i++) { data[i] = str.substring(i * len, i * len + len); } return data; }
- 将字符串数组进行异或运算
public static String handleXOrStringArr(String[] strs){ String result = ""; for (int i = 1;i < strs.length;i++){ if (i == 1){ result = xOr(strs[0],strs[1]); }else { result = xOr(strs[i],result); } } return result; }
- 异或运算
/** * 异或运算 * @param s1 * @param s2 * @return */ public static String xOr(String s1, String s2) { String data = IntArr2String(xOr(String2IntArr(s1),String2IntArr(s2))); return data; } public static int [] xOr(int[] i1,int[] i2){ int[] xor = new int[i1.length]; for (int i = 0;i < i1.length & i < i2.length;i++){ xor[i] = i1[i]^i2[i]; } return xor; }
- 加密工具类
- sm4加密
public static byte[] encodeSMS4(byte[] plaintext, byte[] key) {
byte[] ciphertext = new byte[plaintext.length];
int k = 0;
int plainLen = plaintext.length;
while (k + 16 <= plainLen) {
byte[] cellPlain = new byte[16];
for (int i = 0; i < 16; i++) {
cellPlain[i] = plaintext[k + i];
}
byte[] cellCipher = encode16(cellPlain, key);
for (int i = 0; i < cellCipher.length; i++) {
ciphertext[k + i] = cellCipher[i];
}
k += 16;
}
return ciphertext;
}
调用过程
-
将密钥转换为字节数组,将需要加密计算的字符串转换为16进制字符串并补位
例如:
密钥:"12345678901234567890123456789012"
待加密数据(字符串形式):
6000112018111411003020170930101010120171025000755000000005553//将key转换为字节数组 byte[] keys = TransformUtils.HexStringToByteArr(key); //将加密数据转换为16进制字符串 String hexData = TransformUtils.StringToHexString(data);
-
将转换过的16字符串进行分组
//将数据分组 32位为一组 String[] dataGroup = TransformUtils.dataGrouping(hexData, 32);
-
将分组过的字符串数组逐步进行异或运算
//进行异或运算 String xorData = TransformUtils.handleXOrStringArr(dataGroup);
-
将最后异或结果转化为16进制字符串
//将异或结果转化为16进制字符串 String hexOxrData = TransformUtils.StringToHexString(xorData);
-
取前16字节和后16字节,前16字节进行sm4加密,加密结果与后16字节异或后再加密
//取前16字节和后16字节 两个16进制位表示一个字节 String start32Bit = hexOxrData.substring(0,32); String end32Bit = hexOxrData.substring(hexOxrData.length() - 32,hexOxrData.length()); //前16位进行首次加密 byte[] encodeSMS4_1 = SMS4.encodeSMS4(TransformUtils.HexStringToByteArr(start32Bit), keys); //加密结果与后16位进行异或 String xOrEnd = TransformUtils.xOr(TransformUtils.bytesToHexString(encodeSMS4_1, 16), end32Bit); //异或结果转成字节数组 byte[] bytes = TransformUtils.HexStringToByteArr(xOrEnd); //进行二次加密 byte[] resultByte = SMS4.encodeSMS4(bytes, keys); //将加密结果转换成16进制字符串 并取前八个字符作为mac值返回 String result = TransformUtils.bytesToHexString(resultByte, 16);
-
取前16个字符作为mac值
//取前8字节作为mac值返回 result = result.substring(0,16);
后记
此处是pos终端mac国密(sm4)算法,之前还有一个pos终端mac国际算法(双倍长 3des),两者的区别在于 分组的时候分组长度不太一样,国际是16个16进制字符(8字节)分组,国密是32个16进制字符(16字节)分组,再有不同的地方就是加密算法不同,国密是sm4加密算法,国际是双倍长3des加密算法,其他基本都是一样的,这个加密算法也是根据需求可以进行更改的 ,比如改为RSA 或AES也是可以的,这个就需要双方沟通了 统一加密算法就可以了