1 MD5算法原理
MD5算法的核心原理是利用hash的不可逆性,被加密后的明文无法通过解密函数得到明文,并且一旦明文被改变,加密后的密文也是完全不一样的。有了这个特性之后,支付机构就可以发现被篡改的明文了。MD5算法的过程分为5步:原文补位、添加长度、设置初始值、函数加工、拼接结果。
原文补位
MD5加密算法要求原文的位长对512求余的结果是448,所以原文的位长将被扩展至N*512+448。如果原文长度不满足,需要进行填充,填充的方式是第一位补1,之后补0直到满足条件。
添加长度
然后最后面附加64位存储信息的长度。经过两次操作,最终信息位长=N*512+448+64=(N+1)*512。
设置初始值
MD5底层实现是哈希,哈希结果长度是固定的128位,把这128位分成4组,每组32位。这4组结果有4个初始值A、B、C、D经过不断演变得到,初始值如下:
A=0x01234567
B=0x89ABCDEF
C=0xFEDCBA98
D=0x76543210
函数加工
需要经过4个函数的加工
F(X,Y,Z)=(X & Y) | ((~X) & Z);
G(X,Y,Z)=(X & Z) | (Y & (~Z));
H(X,Y,Z)=X ^ Y ^ Z;
I(X,Y,Z)=Y ^ (X | (~Z));
以512为一组,把信息分组,没一个分组进行4轮加工,以上面的4个常数为起始变量进行计算,重新得到4个变量,以得到的4个变量在进行下一分组运算。
拼接结果
将经过加工的数据拼接起来,得到MD5加密后的数据。
经过MD5加签之后,原文数据有任何改动,验签都是过不了的,请求的数据就会被驳回。了解了原理之后,我们看一下使用MD5加签验签的过程
生成密钥
生成密钥key,客户端和服务端个保留一份,key可以是任意的字符串,不可以泄露。
确定加签参数和拼接规则
客户端和服务端约定需要加签的字段和拼接规则,按照拼接规则把需要加签的字段和密钥一起拼接为字符串。比如加签字段有姓名和银行卡号,拼接规则为姓名+银行卡号+密钥。
生成签名
利用MD5算法,根据拼接后的字符串生成哈希值,MD5生成的哈希值是128位的二进制数,也就是32位的16进制数。
验证签名
MD5算法的底层实现是哈希,哈希是不可逆的(比如10%3=1,1%3=1,所以我们拿到1不能知道原值到底是3还是10),所以验签的过程实际上是按照约定的规则利用密钥把明文重新签名然后比较和客户端上送的是否一致,一致的就验签通过,不一致的验签失败。
2 MD5加签验签JAVA示例
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName MD5Utils
* @Description TODO
* @Author boy
* @Date 2019/8/30 8:29 PM
*/
public class MD5Utils {
static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
static String MD5 = "MD5";//加签方式:MD5
/*
* @Author boy
* @Description 数据签名
* @Date 2019/8/31 1:57 PM
* @Param [data, key]
* @return java.lang.String
*/
public static String sign(String data, String key) throws Exception {
//得到明文的字节数组
byte[] btInput = (data + key).getBytes();
// 创建一个提供信息摘要算法的对象(MD5摘要算法)
MessageDigest messageDigest = MessageDigest.getInstance(MD5);
// 使用指定的字节更新摘要
messageDigest.update(btInput);
// 得到二进制的密文
byte[] encryptData = messageDigest.digest();
// 把密文转换成十六进制的字符串形式
String encryptDataStr = bytesToHex(encryptData);
return encryptDataStr;
}
/*
* @Author boy
* @Description 验签
* @Date 2019/8/31 1:57 PM
* @Param [data, key, sign][明文数据,签名key,接收到的签名]
* @return boolean
*/
public static boolean verifySign(String data, String key, String sign) throws Exception {
//调用加签方法,看加签后的签名是否和接收到的一致
String encryptData = sign(data, key);
if (encryptData.equals(sign)) {
return true;
} else {
return false;
}
}
/*
* @Author boy
* @Description 将byte数组转化为16进制字符串
* @Date 2019/8/31 1:58 PM
* @Param [bytes]
* @return java.lang.String
*/
public static String bytesToHex(byte[] bytes) {
int k = 0;
char[] hexChars = new char[bytes.length * 2];
for (int i = 0; i < bytes.length; i++) {
byte byte0 = bytes[i];
hexChars[k++] = hexDigits[byte0 >>> 4 & 0xf];
hexChars[k++] = hexDigits[byte0 & 0xf];
}
return new String(hexChars);
}
public static void main(String[] args) throws Exception {
Map<String, String> hashMap = new HashMap<>();
String data = "你好!MD5!";
String key = "1234567890abcdef";
String dataSign = MD5Utils.sign(data, key);
hashMap.put("data", data);
hashMap.put("dataSign", dataSign);
System.out.println("明文:" + hashMap.get("data"));
System.out.println("签名:" + hashMap.get("dataSign"));
System.out.println("验签结果:" + MD5Utils.verifySign(data, key, dataSign));
}
}