特征
-
单向性
- 输入数据生成哈希值后无法逆向推导原始数据(如搜索结果提到的“丢掉部分信息的加密方式”)
- 应用场景:存储用户密码时仅保存哈希值(加盐处理更安全)
-
确定性
- 相同输入始终生成相同哈希值,常用于数据完整性校验(如文件传输验证)
-
抗碰撞性
- 极难找到两个不同输入产生相同哈希(搜索结果提到“阿呆找出一奇一偶得到相同结果”需通过算法改进解决)
-
固定输出长度
- SHA-256 固定输出256位(32字节),Tiger算法支持多种输出长度(如192位)
签名的作用
- 使用密钥和哈希算法(如SHA-256、HMAC)生成签名,发送给接口方,接口方根据传过来的参数,对参数重新加密。生成签名,进行对比,防止入参被篡改
java使用类说明
MessageDigest.getInstance() 与 Mac.getInstance() 的区别
- MessageDigest
用于生成数据的单向哈希值(如MD5、SHA-256),仅验证数据完整性,无密钥参与。
示例:MessageDigest.getInstance("SHA-256")生成固定长度的哈希值。 - Mac
用于生成消息认证码(MAC),结合哈希算法和密钥,验证数据完整性和来源真实性。
示例:Mac.getInstance("HmacSHA256")生成带密钥的哈希值 - 使用例子
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(data.getBytes());
byte[] hash = md.digest(); // :ml-citation{ref="1,8" data="citationList"}
SecretKeySpec key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(key); // 初始化密钥
byte[] macHash = mac.doFinal(data.getBytes()); // :ml-citation{ref="2,3" data="citationList"}
-
算法名称格式
MessageDigest使用纯哈希算法名称,如:
"MD5"、"SHA-256"Mac算法名称以 **
Hmac前缀** 标识,如:"HmacSHA1"、`"HmacMD5"
| MessageDigest算法名称 | 描述 | 安全性建议 |
|---|---|---|
| "MD2" | 旧版128位哈希算法,已不安全 已淘汰,仅用于兼容旧系统 | 不推荐用于安全场景 |
| "MD5" | 128位哈希算法,广泛用于非安全场景(如校验文件完整性) | 不推荐用于安全场景 |
| "SHA-1" | 160位哈希算法,已被证明存在碰撞漏洞 | 不推荐用于安全场景 |
| "SHA-224" | SHA-2系列算法,224位哈希值 | 安全(但不如SHA-256常用) |
| "SHA-256" | SHA-2系列算法,256位哈希值,当前主流选择 | 推荐用于常规安全场景 |
| "SHA-384" | SHA-2系列算法,384位哈希值 | 适用于需要更长哈希的场景 |
| "SHA-512" | SHA-2系列算法,512位哈希值 | 适用于高安全性需求 |
| "SHA3-224" | SHA-3系列算法(Java 9+支持),224位哈希值 | 安全,但需Java 9+ |
| "SHA3-256" | SHA-3系列算法(Java 9+支持),256位哈希值 | 安全,适用于未来标准 |
| "SHA3-384" | SHA-3系列算法(Java 9+支持),384位哈希值 | 安全 |
| "SHA3-512" | SHA-3系列算法(Java 9+支持),512位哈希值 | 安全 |
| Mac算法名称 | 描述 |
|---|---|
| HmacSHA1 | 基于SHA-1哈希算法的HMAC实现,安全性较低,建议仅在兼容旧系统时使用。 |
| HmacSHA256 | 基于SHA-256哈希算法的HMAC实现,适用于高安全性场景1。 |
| HmacSHA384 | 基于SHA-384哈希算法的HMAC实现,提供更长的哈希长度。 |
| HmacSHA512 | 基于SHA-512哈希算法的HMAC实现,适用于需要高安全性和抗碰撞的场景。 |
| HmacMD5 | 基于MD5哈希算法的HMAC实现,已不推荐用于安全敏感场景。 |
- 应用场景
MessageDigest: 文件完整性校验(如下载文件MD5比对),密码存储(需结合盐值)
Mac:API请求签名(如OBS接口身份认证),安全通信(验证数据来源,防止篡改)
- 总结
MessageDigest:是单向哈希,用于快速校验数据完整性
Mac: 是带密钥的哈希,用于同时验证数据完整性和来源真实性
对比:在需要防伪造的场景(如API签名),优先使用 Mac;仅需防篡改时,可选择 MessageDigest
java例子
例子1
package org.example.test;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class SignatureUtil {
public static String generateSignature(String data, String secretKey) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
mac.init(secretKeySpec);
byte[] hashBytes = mac.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(hashBytes);
}
public static void main(String[] args) {
try {
String data = "param1=value1¶m2=value2×tamp=1633024800";
String secretKey = "mySecretKey";
String signature = generateSignature(data, secretKey);
System.out.println("Generated Signature: " + signature);
} catch (Exception e) {
e.printStackTrace();
}
}
}
package org.example.test;
public class SignatureValidator {
public static boolean validateSignature(String data, String receivedSignature, String secretKey) throws Exception {
String calculatedSignature = SignatureUtil.generateSignature(data, secretKey);
return calculatedSignature.equals(receivedSignature);
}
public static void main(String[] args) {
try {
String data = "param1=value1¶m2=value2×tamp=1633024800";
String secretKey = "mySecretKey";
String receivedSignature = "a3KcP2fd4sPLskQjAltQiMltaSzBELqt+YcTYJB/UGk=";
boolean isValid = validateSignature(data, receivedSignature, secretKey);
System.out.println("Is Signature Valid: " + isValid);
} catch (Exception e) {
e.printStackTrace();
}
}
}
例子2
package org.example.test;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import java.util.HashMap;
import java.math.BigDecimal;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
public class SHA256Test {
public static String sHA256Encode(String text) {
char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
try {
byte[] btInput = text.getBytes();
// 获得MD5摘要算法的 MessageDigest 对象
MessageDigest mdInst = MessageDigest.getInstance("SHA-256");
// 使用指定的字节更新摘要
mdInst.update(btInput);
// 获得密文
byte[] md = mdInst.digest();
// 把密文转换成十六进制的字符串形式
int j = md.length;
char[] str = new char[j * 2];
int k = 0;
for (byte byte0 : md) {
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str).toLowerCase();
} catch (Exception e) {
return null;
}
}
public static String createSign(TreeMap<String, Object> params) throws Exception {
return createSign(params, new StringBuilder());
}
public static String createSign(TreeMap<String, Object> params, StringBuilder sb) throws Exception {
if (params.isEmpty()) {
// 当参数为空JSON时,直接返回"{}"进行加密
sb.append("{}");
return sHA256Encode(sb.toString());
}
for (String k : params.keySet()) {
Object o = params.get(k);
if (null == o) {
sb.append(k).append("=").append((Object) null).append("&");
continue;
}
if (o instanceof String || o instanceof Integer || o instanceof Long || o instanceof Boolean || o instanceof BigDecimal) {
sb.append(k).append("=").append(o).append("&");
continue;
}
//针对数组类型,只支持字符串和整型
if (o.getClass().isArray()) {
if (o instanceof String[]) {
String[] strAttr = (String[]) o;
Arrays.sort(strAttr);
sb.append(k).append("=").append(JSON.toJSONString(strAttr)).append("&");
continue;
}
if (o instanceof Integer[]) {
Integer[] intAttr = (Integer[]) o;
Arrays.sort(intAttr);
sb.append(k).append("=").append(JSON.toJSONString(intAttr)).append("&");
continue;
}
}
//针对集合类型,只支持List和Set
if (o instanceof Collection) {
if (o instanceof List) {
List list = (List) o;
Collections.sort(list);
sb.append(k).append("=").append(JSON.toJSONString(list)).append("&");
continue;
}
if (o instanceof Set) {
TreeSet treeSet = new TreeSet((Set) o);
sb.append(k).append("=").append(JSON.toJSONString(treeSet)).append("&");
continue;
}
}
//针对Map,需要转TreeMap
if (o instanceof Map) {
TreeMap<String, Object> treeMap = new TreeMap<>();
Map<String, Object> map = (Map) o;
map.forEach(treeMap::put);
createSign(treeMap, sb);
}
}
//如果传入空List,Map等,签名校验失败
if (sb.length() == 0) {
throw new Exception("签名校验失败,入参类型不满足或参数为空");
}
String param = sb.substring(0, sb.length() - 1);
return sHA256Encode(param);
}
public static void main(String[] args) throws Exception {
TreeMap<String, Object> params = new TreeMap<>();
params.put("name", "test");
ArrayList<String> list = new ArrayList<>();
list.add("足球");
list.add("跑步");
params.put("interest", list);
HashMap<Object, Object> extraInfoMap = new HashMap();
extraInfoMap.put("height", "175");
extraInfoMap.put("weight", "130");
params.put("extraInfo", extraInfoMap);
// 4786f2f4a78377f30caddac891bff965850366ac91dd6c6de896aa161230a6e6
System.out.println(createSign(params));
// 验证参数
/**
* 例如body传参
* {
* "name": "test",
* "interest":["足球","跑步"],
* "extraInfo":{
* "height":"175",
* "weight":"130"
* }
* }
*/
String strParam = "{\n" +
"\t"name": "test",\n" +
"\t"interest": [\n" +
"\t\t"足球",\n" +
"\t\t"跑步"\n" +
"\t],\n" +
"\t"extraInfo": {\n" +
"\t\t"height": "175",\n" +
"\t\t"weight": "130"\n" +
"\t}\n" +
"}";
// 签名
String sign = "4786f2f4a78377f30caddac891bff965850366ac91dd6c6de896aa161230a6e6";
// 顺序不对,不使用
TreeMap<String, Object> stringObjectTreeMap = JSONObject.parseObject(strParam, new TypeReference<TreeMap<String, Object>>() {
});
// System.out.println(stringObjectTreeMap);
// 顺序不对,不使用
// TreeMap<String, Object> treeMap = new TreeMap<>(
// JSON.parseObject(strParam, Map.class)
// );
// System.out.println(treeMap);
// 在方法中按照类型处理
String sign1 = createSign(stringObjectTreeMap);
System.out.println(sign1);
// 对比结果
if (sign.equals(sign1)) {
System.out.println("符合结果");
}
}
}
例子3
package org.example.test;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import org.springframework.util.StringUtils;
/**
* 只校验签名,不校验数据
*/
public class SHAHmacTest {
public static void main(String[] args) throws IOException {
// String param = "{\n" +
// "\t"name": "test",\n" +
// "\t"interest": [\n" +
// "\t\t"足球",\n" +
// "\t\t"跑步"\n" +
// "\t],\n" +
// "\t"extraInfo": {\n" +
// "\t\t"height": "175",\n" +
// "\t\t"weight": "130"\n" +
// "\t}\n" +
// "}";
Map<String, String> params = new HashMap<>();
params.put("signMethod","hmac");
params.put("timestamp","2025-03-21 17:36:00");
params.put("apps","test");
//params.put("params",param);
// 1910226362489A1A69EA700B5D32A852
String sign = signParam(params,"mykeys","hmac");
System.out.println(sign);
// 验证参数
// String jsonStr = "{\n" +
// "\t"signMethod": "hmac",\n" +
// "\t"timestamp": "2025-03-21 17:36:00",\n" +
// "\t"apps": "test",\n" +
// "\t"params": {\n" +
// "\t\t"name": "test",\n" +
// "\t\t"interest": [\n" +
// "\t\t\t"足球",\n" +
// "\t\t\t"跑步"\n" +
// "\t\t],\n" +
// "\t\t"extraInfo": {\n" +
// "\t\t\t"height": "175",\n" +
// "\t\t\t"weight": "130"\n" +
// "\t\t}\n" +
// "\t}\n" +
// "}";
String str ="{\n" +
"\t"signMethod": "hmac",\n" +
"\t"timestamp": "2025-03-21 17:36:00",\n" +
"\t"apps": "test",\n" +
"}";
JSONObject obj = JSONObject.parseObject(str);
String signMethod = obj.getString("signMethod");
HashMap<String, String> stringStringHashMap = JSONObject.parseObject(str, new TypeReference<HashMap<String, String>>() {
});
String s = signParam(stringStringHashMap, "mykeys", signMethod);
System.out.println(s);
// 校验结果是否相等
if (s.equals(sign)) {
System.out.println("符合结果");
}
}
/**
* 对参数进行加密,获取API签名结果:sign ;
* @param params appKey timestamp signMethod
* @param secret appSecret
* @param signMethod hmac md5
* @return
* @throws IOException
*/
public static String signParam(Map<String, String> params, String secret, String signMethod) throws IOException {
// 第一步:检查参数是否已经排序
String[] keys = params.keySet().toArray(new String[0]);
Arrays.sort(keys);
// 第二步:把所有参数名和参数值串在一起
StringBuilder query = new StringBuilder();
if ("md5".equals(signMethod)) { //签名的摘要算法,可选值为:hmac,md5
query.append(secret);
}
for (String key : keys) {
String value = params.get(key);
if (StringUtils.hasText(key) && StringUtils.hasText(value)) {
query.append(key).append(value);
}
}
// 第三步:使用MD5/HMAC加密
byte[] bytes;
if ("hmac".equals(signMethod)) {
bytes = encryptHMAC(query.toString(), secret);
} else {
query.append(secret);
bytes = encryptMD5(query.toString());
}
// 第四步: 16进制
return byte2hex(bytes);
}
public static byte[] encryptHMAC(String data, String secret) throws IOException {
byte[] bytes = null;
try {
SecretKey secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacMD5");
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
bytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
} catch (GeneralSecurityException gse) {
throw new IOException(gse.toString());
}
return bytes;
}
public static byte[] encryptMD5(String data) throws IOException {
return encryptMD5(data.getBytes(StandardCharsets.UTF_8));
}
public static byte[] encryptMD5(byte[] data) throws IOException {
byte[] bytes = null;
try {
MessageDigest md = MessageDigest.getInstance("MD5");
bytes = md.digest(data);
} catch (GeneralSecurityException gse) {
throw new IOException(gse.toString());
}
return bytes;
}
public static String byte2hex(byte[] bytes) {
StringBuilder sign = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex.toUpperCase());
}
return sign.toString();
}
}