是否可以使用原始数据的 SHA256 哈希来验证 SHA256withRSA 签名?
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);
// ???
return signature.verify(signature);
可能吗?在 Java 中会怎样?
听别的公司说的,所以没有源码... =(
简短的回答是是。
长答案与将签名与算法标识符包装在一起的编码有关,并且要归档“仅在散列上验证”功能,您必须进行 2 处更改。
首先-将算法标识符添加到签名中:
正如@President James K. Polk 所写,您必须添加一些额外的字节才能获得验证函数输入的正确编码。随心所欲
“EMSA-PKCS1-v1_5”-Padding(此处描述: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 部分,我们需要一个名为“NonewithRSA”的“裸”RSA-scheme,因此您需要像这样更改实例化:
Signature signatureVerifyHash = Signature.getInstance("NonewithRSA");
这些是两次 RSA 签名验证(旧的和“新”的)的结果:
verify the signature with the full document
sigVerified: true
verify the signature with the SHA256 of the document only
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?");
// 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;
}
}
很长一段时间以来,我一直在使用 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);
// ???
return signature.verify(signature);
可能吗?在 Java 中会怎样?
听别的公司说的,所以没有源码... =(
简短的回答是是。
长答案与将签名与算法标识符包装在一起的编码有关,并且要归档“仅在散列上验证”功能,您必须进行 2 处更改。
首先-将算法标识符添加到签名中: 正如@President James K. Polk 所写,您必须添加一些额外的字节才能获得验证函数输入的正确编码。随心所欲 “EMSA-PKCS1-v1_5”-Padding(此处描述: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 部分,我们需要一个名为“NonewithRSA”的“裸”RSA-scheme,因此您需要像这样更改实例化:
Signature signatureVerifyHash = Signature.getInstance("NonewithRSA");
这些是两次 RSA 签名验证(旧的和“新”的)的结果:
verify the signature with the full document
sigVerified: true
verify the signature with the SHA256 of the document only
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?");
// 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;
}
}