两个HMAC-SHA256实现结果差异原因及正确性确认求助
HMAC-SHA256签名结果不一致问题排查
问题背景
需要计算HMAC签名与消息中的签名对比,但得到两个不同结果:
- 消息:
7914073381342284::TestMerchant:TestPayment-1407325143704:1130:EUR:AUTHORISATION:true - 密钥:
44782def547aaa06c910c43932b1eb0c71fc68d9d0c057550c48ec2acf6ba056 - 结果1:
/b1O7eDkBtlZ3I1xH+qMl/I1aRBDel8Y4sbLZXnDKEI= - 结果2:
coqCmt/IZ4E3CzPvMY8zTjQVL5hYJUiBRg8UU+iCWo0=
猜测结果2正确,以下是生成两个结果的Java代码及问题分析。
核心差异:密钥的处理方式
两个代码的本质区别在于密钥的解析逻辑:
生成结果1的代码(错误逻辑)
此代码直接将密钥字符串按UTF-8编码转为字节数组当作HMAC密钥:
import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.util.Base64; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; public class HMAC { static public byte[] calcHmacSha256(byte[] secretKey, byte[] message) { byte[] hmacSha256 = null; try { Mac mac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, "HmacSHA256"); mac.init(secretKeySpec); hmacSha256 = mac.doFinal(message); } catch (Exception e) { throw new RuntimeException("Failed to calculate hmac-sha256", e); } return hmacSha256; } public static void main(String[] args) { String message = "7914073381342284::TestMerchant:TestPayment-1407325143704:1130:EUR:AUTHORISATION:true"; String key = "44782def547aaa06c910c43932b1eb0c71fc68d9d0c057550c48ec2acf6ba056"; try { byte[] hmacSha256 = HMAC.calcHmacSha256(key.getBytes("UTF-8"), message.getBytes("UTF-8")); //Output of HEX System.out.println(String.format("Hex: %064x", new BigInteger(1, hmacSha256))); System.out.println("Base64: " + Base64.getEncoder().encodeToString(hmacSha256)); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } }
问题在于:给定的密钥是64位十六进制字符串,代表32字节的原始二进制密钥,但这段代码将每个十六进制字符(如'4'、'd')当作UTF-8字符转字节,得到的是64字节的密钥,与实际需要的原始密钥完全不符。
生成结果2的代码(正确逻辑)
此代码将密钥字符串按十六进制解码为原始字节数组,符合HMAC-SHA256的密钥要求:
import java.nio.charset.StandardCharsets; import java.security.SignatureException; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; public class HMACValidator { public static void main(String[] args) throws IllegalArgumentException, SignatureException { String message = "7914073381342284::TestMerchant:TestPayment-1407325143704:1130:EUR:AUTHORISATION:true"; String key = "44782def547aaa06c910c43932b1eb0c71fc68d9d0c057550c48ec2acf6ba056"; HMACValidator demo = new HMACValidator(); String result = demo.calculateHMAC(message, key); System.out.println(result); } public static final String HMAC_SHA256_ALGORITHM = "HmacSHA256"; // To calculate the HMAC SHA-256 public String calculateHMAC(String data, String key) throws IllegalArgumentException, SignatureException { try { if (data == null || key == null) { throw new IllegalArgumentException(); } byte[] rawKey = Hex.decodeHex(key.toCharArray()); // Create an hmac_sha256 key from the raw key bytes SecretKeySpec signingKey = new SecretKeySpec(rawKey, HMAC_SHA256_ALGORITHM); // Get an hmac_sha256 Mac instance and initialize with the signing key Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM); mac.init(signingKey); // Compute the hmac on input data bytes byte[] rawHmac = mac.doFinal(data.getBytes(StandardCharsets.UTF_8)); // Base64-encode the hmac return new String(Base64.encodeBase64(rawHmac)); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Missing data or key."); } catch (Exception e) { throw new SignatureException("Failed to generate HMAC : " + e.getMessage()); } } }
这段代码通过Hex.decodeHex(key.toCharArray())将十六进制字符串解码为32字节的原始密钥,这是正确的处理方式,因此生成的结果2是有效的。
在线工具差异原因
不同在线工具的结果不一致,是因为它们默认的密钥解析方式不同:
- 部分工具默认将密钥当作UTF-8字符串处理(和第一个错误代码逻辑一致),导致结果错误;
- 正确的工具允许选择将密钥按十六进制解码(和第二个正确代码逻辑一致),因此得到正确结果。
结论
结果2是正确的签名值,差异的根源在于密钥的解析方式错误:第一个代码错误地将十六进制密钥当作普通UTF-8字符串处理,而正确的做法是将十六进制字符串解码为原始二进制字节作为HMAC密钥。
内容的提问来源于stack exchange,提问作者Christian Rockrohr
相关产品推荐
相关产品推荐

