Java生成HmacSHA256签名因JSON格式差异与正确值不符
Jackson序列化JSON导致HmacSHA256签名不匹配的解决方案
问题根因
HMAC属于消息认证码算法,对输入的字节序列变化高度敏感,参与签名计算的JSON只要存在任意字节差异(包括空格、换行、缩进、字段顺序、特殊字符转义规则的不同),哪怕语义完全一致,最终生成的签名值都会完全不同。你当前使用writerWithDefaultPrettyPrinter()生成的带美化格式JSON,和签名要求的标准JSON字节序列不匹配,是签名错误的核心原因。
可行方案
- 优先采用紧凑无格式JSON(行业通用方案)
绝大多数签名场景都要求使用去掉所有多余空格、换行、缩进的最小化JSON参与计算,从根源上避免格式差异问题。Jackson直接调用默认序列化方法即可输出紧凑格式,不需要使用美化打印相关API:
注意需要同时固定字段序列化顺序,在Payload实体类上添加注解指定和签名规则一致的字段顺序,避免JVM不同版本、类字段定义顺序差异导致的序列化顺序变化:// 移除writerWithDefaultPrettyPrinter()调用,使用默认序列化 String compactJson = new ObjectMapper().writeValueAsString(payload);// 按签名要求的字段顺序排列 @JsonPropertyOrder({"userId", "orderId", "timestamp", "amount"}) public class SignPayload { // 实体字段定义 } - 特殊场景下对齐指定美化格式(仅当签名方强制要求时使用)
如果签名规则明确要求必须使用带缩进换行的美化JSON,不要使用Jackson默认的DefaultPrettyPrinter,需要自定义PrettyPrinter实现,逐一对齐格式细节:包括缩进空格数、换行符类型(\n/\r\n)、冒号/逗号后是否加空格、对象/数组前后的换行规则,直到序列化出的字符串和正确签名对应的基准JSON逐字节完全一致。该方案维护成本极高,非必要不使用。
避坑提示
- 所有字符串转字节的操作必须明确指定
UTF-8编码,避免不同操作系统默认编码不一致导致的字节差异,优化后的签名方法示例:import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.util.HexFormat; public static String hmacWithJava(String algorithm, String data, String key) throws NoSuchAlgorithmException, InvalidKeyException { Mac mac = Mac.getInstance(algorithm); mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), algorithm)); return "sha256=" + HexFormat.of().formatHex(mac.doFinal(data.getBytes(StandardCharsets.UTF_8))); } - 提前确认序列化规则和签名要求对齐:包括null字段是否序列化、日期格式、大数值输出类型(数字/字符串)、转义字符规则,这些细节差异同样会导致签名错误。
内容的提问来源于stack exchange,提问作者rasilvap
相关产品推荐
相关产品推荐

