数字签名----消息验证码 HMAC
·
一、概念
HMAC 全称(Hash-based Message Authentication Code,即基于Hash的消息的认证码)。 - 基本过程为对某个消息,利用提前共享的对称密钥和Hash算法进行加密处理,得到HMAC值。 - 该HMAC值提供方可以证明自己拥有共享密钥的对称密钥,并且消息自身可以利用HMAC确保未经篡改。
二、为什么不直接使用哈希值来进行校验
每个数据哈希值唯一,数据被伪造篡改后,哈希值改变。
例如: xxxxx?name=hahah&hash=547779D1FBA9B6BB0B9AD6F79498228E
假设:hahah 对应哈希值547779D1FBA9B6BB0B9AD6F79498228E
如果同时篡改name和对应的哈希值,则无法验证是否数据被篡改
例如: xxxxx?name=ahahaha&hash=B9AD6F79498228E547779D1FBA9B6BB0
假设:ahahaha对应哈希值B9AD6F79498228E547779D1FBA9B6BB0
三、过程
1、A端将共享密钥分发(B、C端);
2、A端获取要发送的数据的哈希值hashA;
3、A端将hashA通过共享密钥进行加密生成签名;
4、A端发送数据(原始数据、签名)发送给B端
5、B端接收数据,将原始数据按照相同规则生成hash值hashB
6、B端对A端发送的签名进行解密,得到hashA
7、B端对hashB和hashA进行对比,验证,相同则没有被伪造。
四、项目实战
package com.spring.util;
import org.apache.commons.lang3.StringUtils;
import sun.misc.BASE64Encoder;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.TreeMap;
/**
* 签名、校验接口
*
* @author yilei
* @className SignatrueTest
* @date 2021/1/30 17:33
**/
public class SignatrueUtils {
public static final String MAC_ALGORITHM_DEFAULT = "HmacSHA1";
/**
* 密钥生成器(手动保存)
*
* @param
* @return java.lang.String
* @author yilei
* @date 2021-01-30 15:12
*/
public static String generateSecret() throws NoSuchAlgorithmException {
//String secretKey = UUID.randomUUID().toString().replaceAll("-", "");
// 得到一个 指定算法密钥的密钥生成器
KeyGenerator gen = KeyGenerator.getInstance(MAC_ALGORITHM_DEFAULT);
//生成一个密钥
SecretKey secretKey = gen.generateKey();
byte[] encoded = secretKey.getEncoded();
String encBase64 = new BASE64Encoder().encode(encoded);
return encBase64;
}
/**
* 签名生成器
*
* @param data 明文消息
* @param secretKey 密钥
* @return java.lang.String
* @author yilei
* @date 2021-01-30 15:14
*/
public static String getSignature(String data, String secretKey) {
return hamcsha1(data.getBytes(), secretKey.getBytes());
}
/**
* 签名验证
*
* @param secretKey 密钥
* @param signatrue 签名
* @param data 明文消息
* @return boolean
* @author yilei
* @date 2021-01-30 15:18
*/
public static boolean verifySignature(String secretKey, String signatrue, String data) {
if (StringUtils.isBlank(secretKey)) {
return false;
}
return StringUtils.equals(signatrue, hamcsha1(data.getBytes(), secretKey.getBytes()));
}
/**
* 获取基于哈希的消息验证代码
*
* @param data 消息内容字节数组
* @param key 签名密钥字节数组
* @return java.lang.String
* @author yilei
* @date 2021-01-30 15:15
*/
private static String hamcsha1(byte[] data, byte[] key) {
try {
SecretKeySpec signingKey = new SecretKeySpec(key, MAC_ALGORITHM_DEFAULT);
Mac mac = Mac.getInstance(MAC_ALGORITHM_DEFAULT);
mac.init(signingKey);
return byte2hex(mac.doFinal(data));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
return null;
}
/**
* 字节数组转16进制
*
* @param b 字节数组
* @return java.lang.String
* @author yilei
* @date 2021-01-30 15:16
*/
public static String byte2hex(byte[] b) {
StringBuilder hs = new StringBuilder();
String stmp;
for (int n = 0; b != null && n < b.length; n++) {
stmp = Integer.toHexString(b[n] & 0XFF);
if (stmp.length() == 1) {
hs.append('0');
}
hs.append(stmp);
}
return hs.toString().toUpperCase();
}
public static void main(String[] args) throws NoSuchAlgorithmException {
// 1、获取分配密钥
String secretKey = generateSecret();
System.out.println("密钥:" + secretKey);
// 2、参数按指定规则生成初步签名(这里先进行MD5加密,双重加密更安全)
TreeMap<String, String> stringStringTreeMap = new TreeMap<>();
stringStringTreeMap.put("a", "aaaaaa");
stringStringTreeMap.put("daaaa", "202020202");
stringStringTreeMap.put("ab", "2111111");
stringStringTreeMap.put("beee", "100.00");
System.out.println("需要进行签名的参数 " + stringStringTreeMap);
String message = MD5Utils.encodeSign(stringStringTreeMap);
System.out.println("初步签名:" + message);
//3、通过HMACSHA1,生成最终签名
String signatrue = getSignature(message, secretKey);
System.out.println("最终签名signatrue:" + signatrue);
// 4、将消息和签名同时传送,接收方进行校验签名
System.out.println("签名验证结果:" + verifySignature(secretKey, signatrue, message));
}
}
更多推荐



所有评论(0)