package com.zsElectric.boot.common.util; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * HMAC-MD5参数签名工具类 * 严格按照RFC 2104标准的7个步骤实现 * @version 1.0 */ public class HmacMD5Util { private static final int BLOCK_SIZE = 64; // 块大小为64字节 private static final byte IPAD = 0x36; // 内部填充常量 private static final byte OPAD = 0x5C; // 外部填充常量 /** * 根据入参顺序生成签名: OperatorID+Data+TimeStamp+Seq * @param operatorId 操作员ID * @param data 数据内容 * @param timeStamp 时间戳 * @param seq 序列号 * @param sigSecret 签名密钥 * @return 签名结果(大写) * @throws NoSuchAlgorithmException */ public static String genSign( String operatorId, String data, String timeStamp, String seq, String sigSecret) throws NoSuchAlgorithmException { String content = (operatorId + data + timeStamp + seq); return hmacMD5Hex(content, sigSecret); } /** * 出参 * */ public static String genSign( int Ret, String Msg, String Data, String sigSecret) throws NoSuchAlgorithmException { String content = (Ret + Msg + Data).toUpperCase(); return hmacMD5Hex(content, sigSecret); } /** * HMAC-MD5签名生成 * @param data 待签名的消息内容 * @param key 签名密钥 * @return 16字节的HMAC-MD5签名结果 * @throws NoSuchAlgorithmException */ public static byte[] hmacMD5(byte[] data, byte[] key) throws NoSuchAlgorithmException { // 步骤1:在签名密钥后面添加0创建长为64字节的字符串 byte[] k = prepareKey(key); // 步骤2:将密钥与ipad(0x36)做异或运算 byte[] iPadXor = xorWithPad(k, IPAD); // 步骤3:将消息内容附加到第二步的结果字符串的末尾 byte[] firstInput = concatenate(iPadXor, data); // 步骤4:对第三步生成的数据流做MD5运算 byte[] firstHash = md5(firstInput); // 步骤5:将第一步生成的字符串与opad(0x5c)做异或运算 byte[] oPadXor = xorWithPad(k, OPAD); // 步骤6:将第四步的结果附加到第五步的结果字符串的末尾 byte[] secondInput = concatenate(oPadXor, firstHash); // 步骤7:对第六步生成的数据流做MD5运算,输出最终结果 return md5(secondInput); } /** * 步骤1:准备密钥 - 填充或哈希到64字节 * @param key 原始密钥 * @return 64字节的密钥 * @throws NoSuchAlgorithmException */ private static byte[] prepareKey(byte[] key) throws NoSuchAlgorithmException { byte[] result = new byte[BLOCK_SIZE]; if (key.length > BLOCK_SIZE) { // 如果密钥长度超过64字节,先进行MD5哈希 byte[] hashedKey = md5(key); System.arraycopy(hashedKey, 0, result, 0, hashedKey.length); // 剩余部分填充0 for (int i = hashedKey.length; i < BLOCK_SIZE; i++) { result[i] = 0; } } else if (key.length < BLOCK_SIZE) { // 如果密钥长度不足64字节,后面补0 System.arraycopy(key, 0, result, 0, key.length); for (int i = key.length; i < BLOCK_SIZE; i++) { result[i] = 0; } } else { // 密钥正好64字节 result = key.clone(); } return result; } /** * 步骤2/5:密钥与pad常量进行异或运算 * @param key 64字节的密钥 * @param pad 填充常量(IPAD或OPAD) * @return 异或结果 */ private static byte[] xorWithPad(byte[] key, byte pad) { byte[] result = new byte[BLOCK_SIZE]; for (int i = 0; i < BLOCK_SIZE; i++) { result[i] = (byte) (key[i] ^ pad); } return result; } /** * 字节数组拼接 * @param a 第一个字节数组 * @param b 第二个字节数组 * @return 拼接后的字节数组 */ private static byte[] concatenate(byte[] a, byte[] b) { byte[] result = new byte[a.length + b.length]; System.arraycopy(a, 0, result, 0, a.length); System.arraycopy(b, 0, result, a.length, b.length); return result; } /** * MD5哈希计算 * @param input 输入数据 * @return MD5哈希结果(16字节) * @throws NoSuchAlgorithmException */ private static byte[] md5(byte[] input) throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("MD5"); return md.digest(input); } /** * 字节数组转换为十六进制字符串 * @param bytes 字节数组 * @return 十六进制字符串 */ public static String bytesToHex(byte[] bytes) { StringBuilder hexString = new StringBuilder(); for (byte b : bytes) { String hex = Integer.toHexString(0xff & b); if (hex.length() == 1) { hexString.append('0'); } hexString.append(hex); } return hexString.toString().toUpperCase(); } /** * 生成HMAC-MD5签名(十六进制字符串形式) * @param data 消息内容 * @param key 密钥 * @return 32位十六进制签名字符串 * @throws NoSuchAlgorithmException */ public static String hmacMD5Hex(String data, String key) throws NoSuchAlgorithmException { byte[] signature = hmacMD5(data.getBytes(StandardCharsets.UTF_8), key.getBytes(StandardCharsets.UTF_8)); return bytesToHex(signature); } /** * 生成HMAC-MD5签名(十六进制字符串形式) * @param data 消息内容字节数组 * @param key 密钥字节数组 * @return 32位十六进制签名字符串 * @throws NoSuchAlgorithmException */ public static String hmacMD5Hex(byte[] data, byte[] key) throws NoSuchAlgorithmException { byte[] signature = hmacMD5(data, key); return bytesToHex(signature); } /** * 验证HMAC-MD5签名 * @param data 原始消息内容 * @param key 密钥 * @param signature 待验证的签名(十六进制字符串) * @return 验证结果 * @throws NoSuchAlgorithmException */ public static boolean verify(String data, String key, String signature) throws NoSuchAlgorithmException { String calculatedSignature = hmacMD5Hex(data, key); return calculatedSignature.equalsIgnoreCase(signature); } /** * 测试方法 */ public static void main(String[] args) { try { // 测试数据 String data = "KYWxoKWK3w8a8867aXCha+tgVE2cbZ4eR1Dc1YExri06DfZWBpUMAzlhY7rWR5SeU+xCVOauk4F7MxCJLN+5aJCBENCOAZtUksMM7VgsOz0="; String key = "U9xFXjjdYAycq30C"; System.out.println("=== HMAC-MD5签名测试 ==="); System.out.println("原始数据: " + data); System.out.println("密钥: " + key); // 生成签名 long startTime = System.nanoTime(); String signature = hmacMD5Hex(data, key); long signTime = System.nanoTime() - startTime; System.out.println("HMAC-MD5签名: " + signature); System.out.println("签名长度: " + signature.length() + "字符(32字节)"); System.out.println("签名耗时: " + signTime + "纳秒"); // 验证签名 startTime = System.nanoTime(); boolean isValid = verify(data, key, signature); long verifyTime = System.nanoTime() - startTime; System.out.println("签名验证: " + (isValid ? "成功" : "失败")); System.out.println("验证耗时: " + verifyTime + "纳秒"); // 测试不同长度密钥 System.out.println("\n=== 不同长度密钥测试 ==="); testWithDifferentKeyLengths(); // 测试签名一致性 System.out.println("\n=== 签名一致性测试 ==="); testSignatureConsistency(); } catch (Exception e) { e.printStackTrace(); } } /** * 测试不同长度密钥的签名 */ private static void testWithDifferentKeyLengths() throws NoSuchAlgorithmException { String data = "测试数据"; // 短密钥(小于64字节) String shortKey = "short"; String signature1 = hmacMD5Hex(data, shortKey); System.out.println("短密钥签名: " + signature1); // 长密钥(等于64字节) String longKey = "thisIsAExactly64BytesLongKeyUsedForHMACMD5SignatureTest123"; String signature2 = hmacMD5Hex(data, longKey); System.out.println("64字节密钥签名: " + signature2); // 超长密钥(大于64字节) String veryLongKey = "thisIsAVeryLongKeyThatExceedsThe64BytesBlockSizeUsedForHMACMD5SignatureTest123456789"; String signature3 = hmacMD5Hex(data, veryLongKey); System.out.println("超长密钥签名: " + signature3); } /** * 测试签名一致性(相同输入应产生相同输出) */ private static void testSignatureConsistency() throws NoSuchAlgorithmException { String data = "一致性测试数据"; String key = "testKey"; // 多次签名应该结果一致 String signature1 = hmacMD5Hex(data, key); String signature2 = hmacMD5Hex(data, key); String signature3 = hmacMD5Hex(data, key); System.out.println("第一次签名: " + signature1); System.out.println("第二次签名: " + signature2); System.out.println("第三次签名: " + signature3); boolean consistent = signature1.equals(signature2) && signature2.equals(signature3); System.out.println("签名一致性: " + (consistent ? "通过" : "失败")); // 测试数据微小变化会导致签名完全不同 String similarData = "一致性测试数据 "; // 多一个空格 String differentSignature = hmacMD5Hex(similarData, key); System.out.println("微小变化后签名: " + differentSignature); System.out.println("敏感性测试: " + (!signature1.equals(differentSignature) ? "通过" : "失败")); } }