使用 SHA1 和 RSA 与 java.security.Signature 对比 MessageDigest 和 Cipher
我试图了解 Java java.security.Signature 类的作用。 如果我计算 SHA1 消息摘要,然后使用 RSA 加密该摘要,我会得到与要求 Signature 类签署相同内容不同的结果:
// Generate new key
KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
String plaintext = "This is the message being signed";
// Compute signature
Signature instance = Signature.getInstance("SHA1withRSA");
instance.initSign(privateKey);
instance.update((plaintext).getBytes());
byte[] signature = instance.sign();
// Compute digest
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
byte[] digest = sha1.digest((plaintext).getBytes());
// Encrypt digest
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] cipherText = cipher.doFinal(digest);
// Display results
System.out.println("Input data: " + plaintext);
System.out.println("Digest: " + bytes2String(digest));
System.out.println("Cipher text: " + bytes2String(cipherText));
System.out.println("Signature: " + bytes2String(signature));
结果(例如):
输入数据:这是正在签名的消息
摘要:62b0a9ef15461c82766fb5bdaae9edbe4ac2e067
密文:057dc0d2f7f54acc95d3cf5cba9f944619394711003bdd12...
签名:7177c74bbbb871cc0af92e30d2808ebae146f25d3fd8ba1622...
我一定对 Signature 的作用有一个根本性的误解 - 我已经追踪过它,它似乎正在对 MessageDigest 对象调用 update ,按照我的预期将算法设置为 SHA1,然后获取摘要,然后进行加密。 是什么导致结果不同?
编辑:
列奥尼达斯让我检查签名方案是否应该按照我的想法进行。 RFC 中定义了两种类型的签名:
第一个 (PKCS1) 是我上面描述的。 它使用哈希函数创建摘要,然后使用私钥对结果进行加密。
第二种算法使用随机盐值,更加安全但具有不确定性。 如果重复使用相同的密钥,上面代码生成的签名不会改变,所以我认为它不可能是PSS。
编辑:
这是我使用的bytes2string
方法:
private static String bytes2String(byte[] bytes) {
StringBuilder string = new StringBuilder();
for (byte b : bytes) {
String hexString = Integer.toHexString(0x00FF & b);
string.append(hexString.length() == 1 ? "0" + hexString : hexString);
}
return string.toString();
}
I'm trying to understand what the Java java.security.Signature class does. If I compute an SHA1 message digest, and then encrypt that digest using RSA, I get a different result to asking the Signature class to sign the same thing:
// Generate new key
KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
String plaintext = "This is the message being signed";
// Compute signature
Signature instance = Signature.getInstance("SHA1withRSA");
instance.initSign(privateKey);
instance.update((plaintext).getBytes());
byte[] signature = instance.sign();
// Compute digest
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
byte[] digest = sha1.digest((plaintext).getBytes());
// Encrypt digest
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] cipherText = cipher.doFinal(digest);
// Display results
System.out.println("Input data: " + plaintext);
System.out.println("Digest: " + bytes2String(digest));
System.out.println("Cipher text: " + bytes2String(cipherText));
System.out.println("Signature: " + bytes2String(signature));
Results in (for example):
Input data: This is the message being signed
Digest: 62b0a9ef15461c82766fb5bdaae9edbe4ac2e067
Cipher text: 057dc0d2f7f54acc95d3cf5cba9f944619394711003bdd12...
Signature: 7177c74bbbb871cc0af92e30d2808ebae146f25d3fd8ba1622...
I must have a fundamental misunderstanding of what Signature is doing - I've traced through it, and it appears to be calling update on a MessageDigest object, with the algorithm set to SHA1 as I would expect, then getting the digest, then doing the encryption. What's making the results differ?
EDIT:
Leonidas made me check whether the signature scheme is supposed to do what I think it does. There are two types of signature defined in the RFC:
The first of these (PKCS1) is the one I describe above. It uses a hash function to create a digest, and then encrypts the result with a private key.
The second algorithm uses a random salt value, and is more secure but non-deterministic. The signature produced from the code above does not change if the same key is used repeatedly, so I don't think it can be PSS.
EDIT:
Here's the bytes2string
method I was using:
private static String bytes2String(byte[] bytes) {
StringBuilder string = new StringBuilder();
for (byte b : bytes) {
String hexString = Integer.toHexString(0x00FF & b);
string.append(hexString.length() == 1 ? "0" + hexString : hexString);
}
return string.toString();
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
好的,我已经弄清楚发生了什么事。 Leonidas 是对的,加密的不仅仅是哈希值(在 Cipher 类方法的情况下),还有与摘要连接的哈希算法的 ID:
这就是 Cipher 和 Signature 的加密不同的原因。
OK, I've worked out what's going on. Leonidas is right, it's not just the hash that gets encrypted (in the case of the Cipher class method), it's the ID of the hash algorithm concatenated with the digest:
Which is why the encryption by the Cipher and Signature are different.
要产生相同的结果:
To produce the same results:
bytes2String 方法的一个稍微高效的版本是
A slightly more efficient version of the bytes2String method is
呃,在理解你的问题之后:你确定签名方法只创建 SHA1 并对其进行加密吗? GPG 等人提出压缩/清除数据签名。 也许这个 java-signature-alg 还创建了一个可分离/可附加的签名。
Erm, after understanding your question: are you sure that the signature-method only creates a SHA1 and encrypts it? GPG et al offer to compress/clear sign the data. Maybe this java-signature-alg also creates a detachable/attachable signature.
以@Mike Houston 的答案为指针,这里是一个完整的示例代码,用于签名、哈希和加密。
您可以使用 BouncyCastle Provider 或默认的 Sun Provider。
Taking @Mike Houston's answer as pointer, here is a complete sample code that does Signature and Hash and encryption.
You can use BouncyCastle Provider or default Sun Provider.
我有类似的问题,我测试了添加代码并发现了一些有趣的结果。
通过我添加的这段代码,我可以推断出,根据要使用的“提供商”,公司可能会有所不同? (因为加密中包含的数据在所有提供者中并不总是相同)。
我的测试结果。
结论。-
Signature Decipher= ???(trash) + DigestInfo(如果我们知道“trash”的值,则数字签名将相等)
IDE Eclipse 输出...
输入数据:这是正在签名的消息
摘要:62b0a9ef15461c82766fb5bdaae9edbe4ac2e067
摘要信息:3021300906052b0e03021a0500
041462b0a9ef15461c82766fb5bdaae9edbe4ac2e067签名解密:1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003021300906052b0 e03021a0500041462b0a9ef15461c82766fb5bdaae9edbe4ac2e067
代码
I have a similar problem, I tested adding code and found some interesting results.
With this code I add, I can deduce that depending on the "provider" to use, the firm can be different? (because the data included in the encryption is not always equal in all providers).
Results of my test.
Conclusion.-
Signature Decipher= ???(trash) + DigestInfo (if we know the value of "trash", the digital signatures will be equal)
IDE Eclipse OUTPUT...
Input data: This is the message being signed
Digest: 62b0a9ef15461c82766fb5bdaae9edbe4ac2e067
DigestInfo: 3021300906052b0e03021a0500041462b0a9ef15461c82766fb5bdaae9edbe4ac2e067
Signature Decipher: 1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003021300906052b0e03021a0500041462b0a9ef15461c82766fb5bdaae9edbe4ac2e067
CODE
下面的代码(取自我的博客文章 - http ://todayguesswhat.blogspot.com/2021/01/manually-verifying-rsa-sha-signature-in.html )希望有助于理解具有 RSA 签名的标准 SHA 中的内容。 这应该在标准 Oracle JDK 中工作,并且不需要 Bouncy Castle 库。 它使用 sun.security 类来处理解密的签名内容 - 您可以轻松地手动解析。
在下面的示例中,消息摘要算法是 SHA-512,它会生成 64 字节(512 位)校验和。
SHA-1 非常相似 - 但生成 20 字节(160 位)校验和。
Code below (taken from my blog article - http://todayguesswhat.blogspot.com/2021/01/manually-verifying-rsa-sha-signature-in.html ) is hopefully helpful in understanding what is present in a standard SHA with RSA signature. This should work in standard Oracle JDK and does not require Bouncy Castle libraries. It is using the sun.security classes to process the decrypted signature contents - you could just as easily manually parse.
In the example below, the message digest algorithm is SHA-512 which produces a 64 byte (512-bit) checksum.
SHA-1 would be pretty similar - but producing a 20-byte (160-bit) checksum.