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";
问题根因
一共三个核心问题导致结果不匹配:
- 字节数组处理逻辑错误:Java中对字节数组直接调用
toString()不会返回数组对应的内容,只会返回[B@开头的对象内存地址标识,这也是输出中出现%5BB%4079c7ad7f的直接原因([被URL编码为%5BB,@被编码为%40)。 - 日期格式化存在环境兼容问题:
SimpleDateFormat如果不指定Locale,在中文语言环境下会生成中文的星期、月份缩写,和JS端toUTCString()生成的英文格式时间字符串不一致,直接导致加密原文不匹配。 - 编码规则未对齐: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端的执行顺序:
- 生成GMT格式的UTC时间字符串,格式、语言完全匹配
- 拼接
date:前缀得到待签名原文 - 使用apiSecret作为密钥对原文做HmacSHA1计算
- 将HMAC计算结果转为Base64字符串
- 对Base64字符串做URL编码得到最终认证头
内容的提问来源于stack exchange,提问作者Halil Sahin
相关产品推荐
相关产品推荐

