英文:
Is it possible to verify a SHA256withRSA signature with a SHA256 hash of the original data?
问题
长期以来,我一直在使用X509证书处理签名。
签名代码部分:
PrivateKey privateKey = ...
String document = ...
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(document.getBytes());
return signature.sign();
验证代码部分:
PublicKey publicKey = ...
String document = ...
byte[] signature = ...
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(document.getBytes());
return signature.verify(signature);
非常简单。
但最近我听说,可以仅使用文档的SHA256哈希来验证签名,而不是整个文档...
验证代码部分(使用文档哈希):
PublicKey publicKey = ...
byte[] documentHash = MessageDigest.getInstance("SHA-256").digest(document.getBytes());
byte[] signature = ...
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(documentHash);
return signature.verify(signature);
这是否可行?在Java中应该如何实现?
我是从另一家公司听说的,所以无法访问源代码... =(
英文:
For a long time I've been working with signatures using X509 certificates.
PrivateKey privateKey = ...
String document = ...
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(document.getBytes());
return signature.sign();
and verifying...
PublicKey publicKey = ...
String document = ...
byte[] signature = ...
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(document.getBytes());
return signature.verify(signature);
Pretty simple.
But recently I heard that it is possible to verify the signature with only the SHA256 hash of the document, instead of the whole document...
PublicKey publicKey = ...
byte[] documentHash = MessageDigest.getInstance("SHA-256").digest(document.getBytes());
byte[] signature = ...
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
// ???
return signature.verify(signature);
Is it possible? How would it be in Java?
I heard it from another company, so I don't have access to the source code... =(
答案1
得分: 2
短答案是是的。
长答案涉及一种编码,将签名与算法标识符一起封装,以及为了实现“仅验证哈希”功能,您需要进行2个更改。
第一步 - 在签名前添加算法标识符:
正如@President James K. Polk所述,您需要添加一些额外的字节,以正确编码输入到验证函数的签名。由于您需要“EMSA-PKCS1-v1_5”填充(在此处描述:https://www.rfc-editor.org/rfc/rfc3447#page-41),您需要在表示用于计算哈希的算法的一些字节之前添加一些字节。
我有点懒,将必要的字节作为硬编码字节数组前置,所以此版本仅在SHA-256算法上工作 - 如果您使用其他散列算法,您需要更改前置的字节:
String prependSha256String = "3031300D060960864801650304020105000420";
byte[] prependSha256 = hexStringToByteArray(prependSha256String);
int combinedLength = prependSha256.length + documentHash.length;
byte[] documentHashFull = new byte[combinedLength];
System.arraycopy(prependSha256, 0, documentHashFull, 0, prependSha256.length);
System.arraycopy(documentHash, 0, documentHashFull, prependSha256.length, documentHash.length);
第二步 - 使用另一种RSA签名方案:
由于我们已经完成了SHA-256部分,我们需要一个“裸”RSA方案,称为“NonewithRSA”,因此您需要更改实例化,如下所示:
Signature signatureVerifyHash = Signature.getInstance("NonewithRSA");
这是两个RSA签名验证的结果(旧和“新”):
验证完整文档的签名
sigVerified: true
仅验证文档的SHA256签名
sigVerifiedHash: true
以下是完整的工作代码:
import java.security.*;
public class MainSo2 {
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
System.out.println("Is it possible to verify a SHA256withRSA signature with a SHA256 hash of the original data?");
// 创建一个2048位密钥长度的RSA密钥对
KeyPairGenerator rsaGenerator = KeyPairGenerator.getInstance("RSA");
SecureRandom random = new SecureRandom();
rsaGenerator.initialize(2048, random);
KeyPair rsaKeyPair = rsaGenerator.generateKeyPair();
PublicKey publicKey = rsaKeyPair.getPublic();
PrivateKey privateKey = rsaKeyPair.getPrivate();
String document = "The quick brown fox jumps over the lazy dog";
// 签名
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(document.getBytes());
byte[] sig = signature.sign();
// 用完整消息验证
System.out.println("\nverify the signature with the full document");
Signature signatureVerify = Signature.getInstance("SHA256withRSA");
signatureVerify.initVerify(publicKey);
signatureVerify.update(document.getBytes());
boolean sigVerified = signatureVerify.verify(sig);
System.out.println("sigVerified: " + sigVerified);
// 仅验证文档的sha256哈希值
System.out.println("\nverify the signature with the SHA256 of the document only");
byte[] documentHash = MessageDigest.getInstance("SHA-256").digest(document.getBytes());
// 您需要添加一些字节:30 31 30 0D 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20
// 详见https://www.rfc-editor.org/rfc/rfc3447#page-41
// 警告:此字符串仅适用于SHA-256算法 !!
String prependSha256String = "3031300D060960864801650304020105000420";
byte[] prependSha256 = hexStringToByteArray(prependSha256String);
int combinedLength = prependSha256.length + documentHash.length;
byte[] documentHashFull = new byte[combinedLength];
System.arraycopy(prependSha256, 0, documentHashFull, 0, prependSha256.length);
System.arraycopy(documentHash, 0, documentHashFull, prependSha256.length, documentHash.length);
// 让我们验证
Signature signatureVerifyHash = Signature.getInstance("NonewithRSA");
signatureVerifyHash.initVerify(publicKey);
// signatureVerifyHash.update(document.getBytes());
signatureVerifyHash.update(documentHashFull);
boolean sigVerifiedHash = signatureVerifyHash.verify(sig);
System.out.println("sigVerifiedHash: " + sigVerifiedHash);
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16));
}
return data;
}
}
英文:
The short answer is YES.
The long answer has to do with an encoding that wraps the signature together with the algorithm Identifier and to archive the "verify on hash only" functionality you have to do 2 changes.
First - prepend the algorithm identifier to the signature:
As @President James K. Polk wrote you have to add some extra byte to get a correct encoding of the input to verification function. As you need
the "EMSA-PKCS1-v1_5"-Padding (described here: https://www.rfc-editor.org/rfc/rfc3447#page-41) you have to prepend some bytes that represent the
algorithm that was used to calculate the hash.
I'm a bit lazy and prepending the necessary bytes as hard coded byte array and so this version does work only on SHA-256 algorithm - if
you ever use a different hashing algorithm you need to change the prepended bytes:
String prependSha256String = "3031300D060960864801650304020105000420";
byte[] prependSha256 = hexStringToByteArray(prependSha256String);
int combinedLength = prependSha256.length + documentHash.length;
byte[] documentHashFull = new byte[combinedLength];
System.arraycopy(prependSha256, 0, documentHashFull, 0, prependSha256.length);
System.arraycopy(documentHash, 0, documentHashFull, prependSha256.length, documentHash.length);
Second - use another RSA signature scheme:
As we have done the SHA-256 part already we need a "naked" RSA-scheme called "NonewithRSA", so you need to change the instantiation like:
Signature signatureVerifyHash = Signature.getInstance("NonewithRSA");
These are the results of the two RSA signature verifications (old and "new" one):
verify the signature with the full document
sigVerified: true
verify the signature with the SHA256 of the document only
sigVerifiedHash: true
Here is the full working code:
import java.security.*;
public class MainSo2 {
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
System.out.println("Is it possible to verify a SHA256withRSA signature with a SHA256 hash of the original data?");
// create a rsa keypair of 2048 bit keylength
KeyPairGenerator rsaGenerator = KeyPairGenerator.getInstance("RSA");
SecureRandom random = new SecureRandom();
rsaGenerator.initialize(2048, random);
KeyPair rsaKeyPair = rsaGenerator.generateKeyPair();
PublicKey publicKey = rsaKeyPair.getPublic();
PrivateKey privateKey = rsaKeyPair.getPrivate();
String document = "The quick brown fox jumps over the lazy dog";
// sign
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(document.getBytes());
byte[] sig = signature.sign();
// verify with full message
System.out.println("\nverify the signature with the full document");
Signature signatureVerify = Signature.getInstance("SHA256withRSA");
signatureVerify.initVerify(publicKey);
signatureVerify.update(document.getBytes());
boolean sigVerified = signatureVerify.verify(sig);
System.out.println("sigVerified: " + sigVerified);
// verify just the sha256 hash of the document
System.out.println("\nverify the signature with the SHA256 of the document only");
byte[] documentHash = MessageDigest.getInstance("SHA-256").digest(document.getBytes());
// you need to prepend some bytes: 30 31 30 0D 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20
// see https://www.rfc-editor.org/rfc/rfc3447#page-41
// warning: this string is only for SHA-256 algorithm !!
String prependSha256String = "3031300D060960864801650304020105000420";
byte[] prependSha256 = hexStringToByteArray(prependSha256String);
int combinedLength = prependSha256.length + documentHash.length;
byte[] documentHashFull = new byte[combinedLength];
System.arraycopy(prependSha256, 0, documentHashFull, 0, prependSha256.length);
System.arraycopy(documentHash, 0, documentHashFull, prependSha256.length, documentHash.length);
// lets verify
Signature signatureVerifyHash = Signature.getInstance("NonewithRSA");
signatureVerifyHash.initVerify(publicKey);
// signatureVerifyHash.update(document.getBytes());
signatureVerifyHash.update(documentHashFull);
boolean sigVerifiedHash = signatureVerifyHash.verify(sig);
System.out.println("sigVerifiedHash: " + sigVerifiedHash);
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16));
}
return data;
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论