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

Java中HMACSHA256哈希的RSA(PKCS1)签名与.NET实现不一致

问题:Java与.NET的RSA SignHash签名结果不一致

我在Java中对HMACSHA256生成的哈希值,使用SHA256算法+PKCS1 RSA签名填充创建数字签名时,遇到签名结果与供应商.NET实现不匹配的问题:

  • Java的signMessage(先对哈希字符串再次哈希再签名)与.NET的SignDataWithFile结果一致
  • Java的signHash(直接对哈希值签名)与.NET的SignHashWithFile结果完全不同

双方代码

Java代码

String secretKey = "1n6pobYP5+BZHgEdFCtNibcb1eykysLMbWMV2fJD0OwfmY3TbtRlUaRWu8A7Fp0dt8O+TCIl3LABlUrW48FFUg==";
private String hmacSha256(String data, String key) throws Exception {
    Mac mac = Mac.getInstance("HmacSHA256");
    SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
    mac.init(secretKeySpec);
    byte[] hmacSha256Bytes = mac.doFinal(data.getBytes("UTF-8"));
    return Base64.getEncoder().encodeToString(hmacSha256Bytes);
}
private String signHash(String hash, PrivateKey privateKey) throws Exception {
    Signature signature = Signature.getInstance("SHA256withRSA", "BC");
    signature.initSign(privateKey);
    signature.update(Base64.getDecoder().decode(hash));
    byte[] signedBytes = signature.sign();
    return Base64.getEncoder().encodeToString(signedBytes);
}
private String signMessage(String hashedMsg, PrivateKey privateKey) throws Exception {
    Signature signature = Signature.getInstance("SHA256withRSA", "BC");
    signature.initSign(privateKey);
    signature.update(hashedMsg.getBytes("UTF-8"));
    byte[] signedBytes = signature.sign();
    return Base64.getEncoder().encodeToString(signedBytes);
}

.NET代码

String secretKey = "1n6pobYP5+BZHgEdFCtNibcb1eykysLMbWMV2fJD0OwfmY3TbtRlUaRWu8A7Fp0dt8O+TCIl3LABlUrW48FFUg==";
public static string HashInstruction(string message, string key)
{
    using HMACSHA256 hMACSHA = new HMACSHA256(Encoding.UTF8.GetBytes(key));
    return Convert.ToBase64String(hMACSHA.ComputeHash(Encoding.UTF8.GetBytes(message)));
}
public static string SignHashWithFile(string message, string privateKeyFile)
{
    byte[] array = ReadPrivateFile(privateKeyFile);
    RSA rSA = RSA.Create();
    rSA.ImportRSAPrivateKey(array, out var _);
    byte[] hash = Convert.FromBase64String(message);
    byte[] inArray = rSA.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
    return Convert.ToBase64String(inArray);
}
public static string SignDataWithFile(string message, string privateKeyFile)
{
    byte[] array = ReadPrivateFile(privateKeyFile);
    RSA rSA = RSA.Create();
    rSA.ImportRSAPrivateKey(array, out var _);
    byte[] data = Encoding.ASCII.GetBytes(message);
    byte[] inArray = rSA.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
    return Convert.ToBase64String(inArray);
}

样本值

.NET样本值

  • 哈希前数据:512|PUB55442201|3555101122526|20000
  • 哈希后数据:vGEWCW/XP7NPFIrY4sOo+jE4Q713RfVlKV/VV8ZQG6I=
  • signHash签名:m3CHGVvBy90CVVnPzzFYQAekN6oy/isfz+fAVyXsJ/COTZ/fCMemANy0Q+yWlh/WUkSGbS7SQrc/33m+Wy69XrctOYSZcWMZpGFGBwBrCkg+wmscBfYItBqKlqmbQr2W0CIxOJlj5fLTeWYQ0MLCrMoBB9g1sB93eECYy9G5MzNfrRQaHVVzQrvBdyphABf8GsIuOzfjTOca5jGCZq6+0i/Xnb49jpkr3bPG3JP2+HbwKbyeqzB59pQaQ1+ye1pE7w85b0Sd3WDSfyjmYDBcNUooaMzekROaR8GPvlerhr1LbXwzvhoDON3ZJhFjerSikcrrM1JeL+s0DOCSQdvsBg==
  • signData签名:wLDprhCgUVG3V0pgU6DCZDQyoHf3VSQ7PMs9T6Ns3OM/lIz2cvACSVFJIs5W5sR7NN/jcp8k6yjQreq9SuSi9EPJsHFNlsqDjfqt78KOkqVD41PlFKRguPi9caMskRSKF/Ja1npXEweQw0MLgxGhs1sOffEzgk7Tqq4N3p2LN4mxgBtxoK3S+HfybPbHKpeSqx6Y0dVfz+Axa3v0/ChNU1+ZHA68H7+j7opdtaXTsx4dzodMvzdpfmFOpC9ye+mhmbJWBfhHDUd+AtIgLSCUZ0v+YhTLFlFY7hUEYRMqk3/tAxLw6dshXla9TSyzkJe3lijJabWGaMGqDgGC+/0puw==

Java样本值

  • 哈希前数据:512|PUB55442201|3555101122526|20000
  • 哈希后数据:vGEWCW/XP7NPFIrY4sOo+jE4Q713RfVlKV/VV8ZQG6I=
  • signHash签名:W1U84sux5el9Tij8Dd9Tu2VbcXC2/BUwZFn5qeYh9M1D/ym6WjahyseTqUb6QFLuy1I9AAQN7p71NvYm1XpKKSGlJMl97cyHPijH3vj/+ydceLWm6Z4cIcJHrZsRld9EYNfYIW7e3h7UVQRwexZaNs+PjcWDAtuUWZevCGnQNzu20D3dIhdJdcZUp/J0rRO0rrWl9njXWDshiZz9mnVYx2wnoOJC6+2j2UBQOezupBzA5ZcvWaTrJhnes0l8aKhgwcdfb59ifuwhvcrFKBTxLkFVI7eMJslluHcFe2BYVjhmOQkqVfbkTYXgBqEsiQjCNdHlYsY15ZSbVDNpt9fthA==
  • signData签名:wLDprhCgUVG3V0pgU6DCZDQyoHf3VSQ7PMs9T6Ns3OM/lIz2cvACSVFJIs5W5sR7NN/jcp8k6yjQreq9SuSi9EPJsHFNlsqDjfqt78KOkqVD41PlFKRguPi9caMskRSKF/Ja1npXEweQw0MLgxGhs1sOffEzgk7Tqq4N3p2LN4mxgBtxoK3S+HfybPbHKpeSqx6Y0dVfz+Axa3v0/ChNU1+ZHA68H7+j7opdtaXTsx4dzodMvzdpfmFOpC9ye+mhmbJWBfhHDUd+AtIgLSCUZ0v+YhTLFlFY7hUEYRMqk3/tAxLw6dshXla9TSyzkJe3lijJabWGaMGqDgGC+/0puw==

问题原因

核心差异在于双方对“签名哈希值”的处理逻辑不同:

  1. .NET的SignHashWithFile方法:传入的是HMACSHA256生成的原始哈希字节,方法会自动按照PKCS#1 v1.5标准,给哈希值加上SHA256对应的ASN.1编码前缀,再进行RSA签名,不会对输入哈希值再次计算哈希。
  2. Java的signHash方法:使用SHA256withRSA算法,该算法会先对输入的字节数组做一次SHA256哈希,再进行PKCS#1签名。也就是说,这里实际签名的是HMAC哈希值的SHA256哈希,而非HMAC哈希本身,自然和.NET结果不一致。

修复方案

修改Java的signHash方法,使用NONEwithRSA算法(不自动做哈希),手动构造PKCS#1 v1.5要求的SHA256签名格式(前缀+原始哈希值):

import org.bouncycastle.util.encoders.Hex;
import java.nio.ByteBuffer;

private String signHash(String hash, PrivateKey privateKey) throws Exception {
    // 解码Base64格式的HMAC哈希值
    byte[] hmacHashBytes = Base64.getDecoder().decode(hash);
    
    // PKCS#1 v1.5中SHA256的固定ASN.1前缀(十六进制转字节)
    byte[] pkcs1Sha256Prefix = Hex.decodeHex("3031300D060960864801650304020105000420");
    
    // 拼接前缀和原始哈希值,构造符合PKCS#1标准的签名输入
    byte[] encodedHash = ByteBuffer.allocate(pkcs1Sha256Prefix.length + hmacHashBytes.length)
            .put(pkcs1Sha256Prefix)
            .put(hmacHashBytes)
            .array();
    
    // 使用NONEwithRSA签名,仅做RSA加密不自动哈希
    Signature signature = Signature.getInstance("NONEwithRSA", "BC");
    signature.initSign(privateKey);
    signature.update(encodedHash);
    byte[] signedBytes = signature.sign();
    
    return Base64.getEncoder().encodeToString(signedBytes);
}

修改后,Java的signHash方法生成的签名将与.NET的SignHashWithFile完全一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 14:55:55