You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript实现的HmacSHA1加密算法适配Java端结果不匹配问题

问题概述

需要将一段JavaScript编写的加密算法适配到Java端,算法生成的内容将作为认证请求头用于服务调用,多次尝试不同实现方式后,始终无法得到与JS端一致的正确结果。

JavaScript端原加密算法实现

function doFunction() {
    apiSecret = "ZDhhODhlOTI2ZjFmNGQ5MDlhMzg5Y2JhZTQyOGUzNDY=";
    date = (new Date()).toUTCString();
    signatureContentString = 'date: ' + date;
    signatureString = CryptoJS.HmacSHA1(signatureContentString, apiSecret).toString(CryptoJS.enc.Base64);
    authHeader = encodeURIComponent(signatureString);
    alert(authHeader);
}

初始Java端实现代码

public String createCredential(){
    Date currentDate = new Date();
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat(Constants.RFC1123_PATTERN);
    simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
    String date = simpleDateFormat.format(currentDate);
    String signatureContentString = "date: "+date;
    byte[] bytes = HmacUtils.hmacSha1(Constants.apiSecret, signatureContentString);
    byte[] encode = Base64.getEncoder().encode(bytes);
    String encoded = URLEncoder.encode(encode.toString(), StandardCharsets.UTF_8);
    return  encoded;
}

两端输出对比

  • JavaScript端运行示例输出:rJxRUgl1%2Bxj5UZSC9rZAHxSl7fw%3D
  • Java端运行示例输出:%5BB%4079c7ad7f

两者输出格式完全不匹配,实现中用到的RFC1123_PATTERN常量定义如下:

public static final String RFC1123_PATTERN = "EEE, dd MMM yyyy HH:mm:ss z";

问题根因

一共三个核心问题导致结果不匹配:

  1. 字节数组处理逻辑错误:Java中对字节数组直接调用toString()不会返回数组对应的内容,只会返回[B@开头的对象内存地址标识,这也是输出中出现%5BB%4079c7ad7f的直接原因([被URL编码为%5BB,@被编码为%40)。
  2. 日期格式化存在环境兼容问题:SimpleDateFormat如果不指定Locale,在中文语言环境下会生成中文的星期、月份缩写,和JS端toUTCString()生成的英文格式时间字符串不一致,直接导致加密原文不匹配。
  3. 编码规则未对齐:CryptoJS传入字符串密钥和待加密内容时,默认使用UTF-8编码解析为字节,原实现未显式指定编码,可能受服务器默认编码影响导致计算结果偏差。

修正后可对齐的Java实现

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import org.apache.commons.codec.digest.HmacUtils;
import java.util.Base64;

public String createCredential(){
    // 强制指定英文Locale,保证生成的时间字符串和JS端UTC格式完全一致
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat(Constants.RFC1123_PATTERN, Locale.ENGLISH);
    simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
    String date = simpleDateFormat.format(new Date());
    String signatureContentString = "date: " + date;
    
    // 密钥、待签名内容统一用UTF-8编码取字节,对齐CryptoJS默认逻辑
    byte[] hmacBytes = HmacUtils.hmacSha1(
            Constants.apiSecret.getBytes(StandardCharsets.UTF_8),
            signatureContentString.getBytes(StandardCharsets.UTF_8)
    );
    
    // HMAC结果先转Base64字符串,禁止直接对字节数组调用toString()
    String base64Sign = new String(Base64.getEncoder().encode(hmacBytes), StandardCharsets.UTF_8);
    
    // 最后对Base64签名做URL编码,和JS端encodeURIComponent逻辑对齐
    return URLEncoder.encode(base64Sign, StandardCharsets.UTF_8);
}

逻辑对齐说明

修正后的实现严格对齐JS端的执行顺序:

  1. 生成GMT格式的UTC时间字符串,格式、语言完全匹配
  2. 拼接date: 前缀得到待签名原文
  3. 使用apiSecret作为密钥对原文做HmacSHA1计算
  4. 将HMAC计算结果转为Base64字符串
  5. 对Base64字符串做URL编码得到最终认证头

内容的提问来源于stack exchange,提问作者Halil Sahin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 08:48:17