哈希加密算法

63 阅读8分钟

特征

  1. 单向性

    • 输入数据生成哈希值后无法逆向推导原始数据(如搜索结果提到的“丢掉部分信息的加密方式”)
    • 应用场景:存储用户密码时仅保存哈希值(加盐处理更安全)
  2. 确定性

    • 相同输入始终生成相同哈希值,常用于数据完整性校验(如文件传输验证)
  3. 抗碰撞性

    • 极难找到两个不同输入产生相同哈希(搜索结果提到“阿呆找出一奇一偶得到相同结果”需通过算法改进解决)
  4. 固定输出长度

    • 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&param2=value2&timestamp=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&param2=value2&timestamp=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();
    }
}