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==
问题原因
核心差异在于双方对“签名哈希值”的处理逻辑不同:
- .NET的
SignHashWithFile方法:传入的是HMACSHA256生成的原始哈希字节,方法会自动按照PKCS#1 v1.5标准,给哈希值加上SHA256对应的ASN.1编码前缀,再进行RSA签名,不会对输入哈希值再次计算哈希。 - 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
相关产品推荐
相关产品推荐

