浏览代码

fix: 回调签名验证从HMAC-SHA256改为MD5,匹配哈哈平台签名规则

哈哈平台签名规则为参数字符串末尾拼接 &appsecret=<secret> 后做 MD5,
此前错误使用了 HMAC-SHA256 导致所有回调签名验证失败。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline 2 周之前
父节点
当前提交
bb8a2ffa81
共有 1 个文件被更改,包括 19 次插入38 次删除
  1. 19 38
      haha-service/src/main/java/com/haha/service/impl/HahaCallbackServiceImpl.java

+ 19 - 38
haha-service/src/main/java/com/haha/service/impl/HahaCallbackServiceImpl.java

@@ -40,17 +40,11 @@ import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
 import java.time.LocalDateTime;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-
 @Slf4j
 @Service
 public class HahaCallbackServiceImpl implements HahaCallbackService {
@@ -561,18 +555,18 @@ public class HahaCallbackServiceImpl implements HahaCallbackService {
         }
 
         try {
-            // 标准 HMAC-SHA256 签名验证算法:
+            // 哈哈平台签名验证算法:
             // 1. 排除 sign、sign_type 等签名相关字段
             // 2. 剩余参数按 key 字典序升序排列
             // 3. 拼接为 key1=value1&key2=value2&...&keyN=valueN 格式
-            // 4. 使用 appSecret 作为密钥计算 HMAC-SHA256
-            // 5. 将结果转换为小写十六进制字符串,与 sign 参数比对
-            //
-            // 注意:如果哈哈平台的签名算法不同(如 MD5、拼接方式差异),
-            // 请根据平台文档调整下面的实现。
+            // 4. 末尾拼接 &appsecret=<secret>
+            // 5. 对拼接后的字符串进行 MD5,得到签名值
+            // 参考: haha-sdk SignUtils.generateSign()
 
             String signContent = buildSignContent(params);
-            String computedSign = hmacSha256(signContent, appSecret);
+            // 哈哈平台签名规则: 在参数字符串末尾拼接 &appsecret=<secret>,然后 MD5
+            String signStr = signContent + "&appsecret=" + appSecret;
+            String computedSign = md5(signStr);
 
             boolean isValid = computedSign.equalsIgnoreCase(receivedSign);
             if (!isValid) {
@@ -583,12 +577,6 @@ public class HahaCallbackServiceImpl implements HahaCallbackService {
             }
             return isValid;
 
-        } catch (NoSuchAlgorithmException e) {
-            log.error("HMAC-SHA256 算法不可用", e);
-            return false;
-        } catch (InvalidKeyException e) {
-            log.error("签名密钥无效", e);
-            return false;
         } catch (Exception e) {
             log.error("签名验证异常", e);
             return false;
@@ -610,27 +598,20 @@ public class HahaCallbackServiceImpl implements HahaCallbackService {
     }
 
     /**
-     * HMAC-SHA256 计算
+     * MD5 加密(32位小写十六进制)
      */
-    private String hmacSha256(String data, String key)
-            throws NoSuchAlgorithmException, InvalidKeyException {
-        Mac mac = Mac.getInstance("HmacSHA256");
-        SecretKeySpec secretKeySpec = new SecretKeySpec(
-                key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
-        mac.init(secretKeySpec);
-        byte[] result = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
-        return bytesToHex(result);
-    }
-
-    /**
-     * 字节数组转小写十六进制字符串
-     */
-    private String bytesToHex(byte[] bytes) {
-        StringBuilder sb = new StringBuilder();
-        for (byte b : bytes) {
-            sb.append(String.format("%02x", b));
+    private String md5(String str) {
+        try {
+            java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
+            byte[] bytes = md.digest(str.getBytes(java.nio.charset.StandardCharsets.UTF_8));
+            StringBuilder sb = new StringBuilder();
+            for (byte b : bytes) {
+                sb.append(String.format("%02x", b));
+            }
+            return sb.toString();
+        } catch (java.security.NoSuchAlgorithmException e) {
+            throw new RuntimeException("MD5 算法不可用", e);
         }
-        return sb.toString();
     }
 
     private void saveDeviceStatusToRedis(String deviceId, String activityId, String doorStatus, String openType, String outUserId) {