从文件摘要创建 pkcs7 签名
Create pkcs7 signature from file digest
目前我有一个客户端-服务器应用程序,给定一个 PDF 文件,对其进行签名(使用服务器证书),将签名附加到原始文件并将 returns 输出返回给客户端(所有这是通过 PDFBox 实现的)。
我有一个签名处理程序,这是我的外部签名支持(其中内容是 PDF 文件)
public byte[] sign(InputStream content) throws IOException {
try {
System.out.println("Generating CMS signed data");
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
ContentSigner sha1Signer = new JcaContentSignerBuilder("Sha1WithRSA").build(privateKey);
generator.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build())
.build(sha1Signer, new X509CertificateHolder(certificate.getEncoded())));
CMSTypedData cmsData = new CMSProcessableByteArray(IOUtils.toByteArray(content));
CMSSignedData signedData = generator.generate(cmsData, false);
return signedData.getEncoded();
} catch (GeneralSecurityException e) {
throw new IOException(e);
} catch (CMSException e) {
throw new IOException(e);
} catch (OperatorCreationException e) {
throw new IOException(e);
}
}
它工作正常,但我在想 - 如果 PDF 文件太大而无法上传怎么办?例如:100mb...这将需要很长时间!
鉴于此,我想弄清楚,如果不是签署 PDF 文件,是否可以只签署该文件的哈希值(ex SHA1),而不是客户端最后将它们放在一起?
更新:
我一直在想办法,现在我的签名方法是:
@Override
public byte[] sign(InputStream content) throws IOException {
// testSHA1WithRSAAndAttributeTable
try {
MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
List<Certificate> certList = new ArrayList<Certificate>();
CMSTypedData msg = new CMSProcessableByteArray(IOUtils.toByteArray(content));
certList.add(certificate);
Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
Attribute attr = new Attribute(CMSAttributes.messageDigest,
new DERSet(new DEROctetString(md.digest(IOUtils.toByteArray(content)))));
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(attr);
SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));
AlgorithmIdentifier sha1withRSA = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(certificate.getEncoded());
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);
gen.addSignerInfoGenerator(builder.build(
new BcRSAContentSignerBuilder(sha1withRSA,
new DefaultDigestAlgorithmIdentifierFinder().find(sha1withRSA))
.build(PrivateKeyFactory.createKey(privateKey.getEncoded())),
new JcaX509CertificateHolder(cert)));
gen.addCertificates(certs);
CMSSignedData s = gen.generate(new CMSAbsentContent(), false);
return new CMSSignedData(msg, s.getEncoded()).getEncoded();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new IOException(e);
}
}
我正在使用 pdfbox 将签名与 PDF 合并
ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(output);
byte[] cmsSignature = sign(externalSigning.getContent());
externalSigning.setSignature(cmsSignature);
问题是 Adobe 说签名无效,因为 "document has been altered or corrupted since it was signed"。
有人可以帮忙吗?
OP 在他的更新中几乎是正确的,只有两个错误:
他尝试读取InputStream
参数内容两次:
CMSTypedData msg = new CMSProcessableByteArray(IOUtils.toByteArray(content));
[...]
Attribute attr = new Attribute(CMSAttributes.messageDigest,
new DERSet(new DEROctetString(md.digest(IOUtils.toByteArray(content)))));
因此,在第二次尝试返回空值 byte[]
之前,所有数据都已从流中读取。因此消息摘要属性包含错误的哈希值。
他以一种复杂的方式创建了最终的 CMS 容器:
return new CMSSignedData(msg, s.getEncoded()).getEncoded();
将后者减少到实际需要的,事实证明不再需要CMSTypedData msg
了。因此,前者被隐式解析。
在 re-arranging 摘要计算到方法顶部之后,另外切换到 SHA256(因为 SHA1 在许多情况下已被弃用,我更喜欢使用不同的哈希算法)并允许证书 chain
而不是单个 certificate
,该方法如下所示:
// Digest generation step
MessageDigest md = MessageDigest.getInstance("SHA256", "BC");
byte[] digest = md.digest(IOUtils.toByteArray(content));
// Separate signature container creation step
List<Certificate> certList = Arrays.asList(chain);
JcaCertStore certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
Attribute attr = new Attribute(CMSAttributes.messageDigest,
new DERSet(new DEROctetString(digest)));
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(attr);
SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));
AlgorithmIdentifier sha256withRSA = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256withRSA");
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(chain[0].getEncoded());
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);
gen.addSignerInfoGenerator(builder.build(
new BcRSAContentSignerBuilder(sha256withRSA,
new DefaultDigestAlgorithmIdentifierFinder().find(sha256withRSA))
.build(PrivateKeyFactory.createKey(pk.getEncoded())),
new JcaX509CertificateHolder(cert)));
gen.addCertificates(certs);
CMSSignedData s = gen.generate(new CMSAbsentContent(), false);
return s.getEncoded();
(CreateSignature方法signWithSeparatedHashing
)
在相当小的签名代码框架中使用
void sign(PDDocument document, OutputStream output, SignatureInterface signatureInterface) throws IOException
{
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName("Example User");
signature.setLocation("Los Angeles, CA");
signature.setReason("Testing");
signature.setSignDate(Calendar.getInstance());
document.addSignature(signature);
ExternalSigningSupport externalSigning =
document.saveIncrementalForExternalSigning(output);
byte[] cmsSignature = signatureInterface.sign(externalSigning.getContent());
externalSigning.setSignature(cmsSignature);
}
(CreateSignature方法sign
)
像这样
try ( InputStream resource = getClass().getResourceAsStream("test.pdf");
OutputStream result = new FileOutputStream(new File(RESULT_FOLDER, "testSignedWithSeparatedHashing.pdf"));
PDDocument pdDocument = PDDocument.load(resource) )
{
sign(pdDocument, result, data -> signWithSeparatedHashing(data));
}
(CreateSignature测试方法testSignWithSeparatedHashing
)
生成正确签名的 PDF,至少与所讨论的证书和私钥适用于手头的任务一样正确。
一句话:
OP 使用了 IOUtils.toByteArray(content))
(我在上面的代码中也是如此)。但考虑到 OP 的开始评论
what if the PDF file is too big to be uploaded? ex: 100mb
这样做并不是一个好主意,因为它会将一个大文件一次加载到内存中,只是为了散列。如果真的想考虑一个应用程序的资源足迹,应该一次读取几 KB 的流并使用 MessageDigest.update
连续消化数据,最后只使用 MessageDigest.digest
来获得结果哈希值。
目前我有一个客户端-服务器应用程序,给定一个 PDF 文件,对其进行签名(使用服务器证书),将签名附加到原始文件并将 returns 输出返回给客户端(所有这是通过 PDFBox 实现的)。
我有一个签名处理程序,这是我的外部签名支持(其中内容是 PDF 文件)
public byte[] sign(InputStream content) throws IOException {
try {
System.out.println("Generating CMS signed data");
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
ContentSigner sha1Signer = new JcaContentSignerBuilder("Sha1WithRSA").build(privateKey);
generator.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build())
.build(sha1Signer, new X509CertificateHolder(certificate.getEncoded())));
CMSTypedData cmsData = new CMSProcessableByteArray(IOUtils.toByteArray(content));
CMSSignedData signedData = generator.generate(cmsData, false);
return signedData.getEncoded();
} catch (GeneralSecurityException e) {
throw new IOException(e);
} catch (CMSException e) {
throw new IOException(e);
} catch (OperatorCreationException e) {
throw new IOException(e);
}
}
它工作正常,但我在想 - 如果 PDF 文件太大而无法上传怎么办?例如:100mb...这将需要很长时间! 鉴于此,我想弄清楚,如果不是签署 PDF 文件,是否可以只签署该文件的哈希值(ex SHA1),而不是客户端最后将它们放在一起?
更新:
我一直在想办法,现在我的签名方法是:
@Override
public byte[] sign(InputStream content) throws IOException {
// testSHA1WithRSAAndAttributeTable
try {
MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
List<Certificate> certList = new ArrayList<Certificate>();
CMSTypedData msg = new CMSProcessableByteArray(IOUtils.toByteArray(content));
certList.add(certificate);
Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
Attribute attr = new Attribute(CMSAttributes.messageDigest,
new DERSet(new DEROctetString(md.digest(IOUtils.toByteArray(content)))));
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(attr);
SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));
AlgorithmIdentifier sha1withRSA = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(certificate.getEncoded());
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);
gen.addSignerInfoGenerator(builder.build(
new BcRSAContentSignerBuilder(sha1withRSA,
new DefaultDigestAlgorithmIdentifierFinder().find(sha1withRSA))
.build(PrivateKeyFactory.createKey(privateKey.getEncoded())),
new JcaX509CertificateHolder(cert)));
gen.addCertificates(certs);
CMSSignedData s = gen.generate(new CMSAbsentContent(), false);
return new CMSSignedData(msg, s.getEncoded()).getEncoded();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new IOException(e);
}
}
我正在使用 pdfbox 将签名与 PDF 合并
ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(output);
byte[] cmsSignature = sign(externalSigning.getContent());
externalSigning.setSignature(cmsSignature);
问题是 Adobe 说签名无效,因为 "document has been altered or corrupted since it was signed"。 有人可以帮忙吗?
OP 在他的更新中几乎是正确的,只有两个错误:
他尝试读取
InputStream
参数内容两次:CMSTypedData msg = new CMSProcessableByteArray(IOUtils.toByteArray(content)); [...] Attribute attr = new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(md.digest(IOUtils.toByteArray(content)))));
因此,在第二次尝试返回空值
byte[]
之前,所有数据都已从流中读取。因此消息摘要属性包含错误的哈希值。他以一种复杂的方式创建了最终的 CMS 容器:
return new CMSSignedData(msg, s.getEncoded()).getEncoded();
将后者减少到实际需要的,事实证明不再需要CMSTypedData msg
了。因此,前者被隐式解析。
在 re-arranging 摘要计算到方法顶部之后,另外切换到 SHA256(因为 SHA1 在许多情况下已被弃用,我更喜欢使用不同的哈希算法)并允许证书 chain
而不是单个 certificate
,该方法如下所示:
// Digest generation step
MessageDigest md = MessageDigest.getInstance("SHA256", "BC");
byte[] digest = md.digest(IOUtils.toByteArray(content));
// Separate signature container creation step
List<Certificate> certList = Arrays.asList(chain);
JcaCertStore certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
Attribute attr = new Attribute(CMSAttributes.messageDigest,
new DERSet(new DEROctetString(digest)));
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(attr);
SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));
AlgorithmIdentifier sha256withRSA = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256withRSA");
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(chain[0].getEncoded());
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);
gen.addSignerInfoGenerator(builder.build(
new BcRSAContentSignerBuilder(sha256withRSA,
new DefaultDigestAlgorithmIdentifierFinder().find(sha256withRSA))
.build(PrivateKeyFactory.createKey(pk.getEncoded())),
new JcaX509CertificateHolder(cert)));
gen.addCertificates(certs);
CMSSignedData s = gen.generate(new CMSAbsentContent(), false);
return s.getEncoded();
(CreateSignature方法signWithSeparatedHashing
)
在相当小的签名代码框架中使用
void sign(PDDocument document, OutputStream output, SignatureInterface signatureInterface) throws IOException
{
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName("Example User");
signature.setLocation("Los Angeles, CA");
signature.setReason("Testing");
signature.setSignDate(Calendar.getInstance());
document.addSignature(signature);
ExternalSigningSupport externalSigning =
document.saveIncrementalForExternalSigning(output);
byte[] cmsSignature = signatureInterface.sign(externalSigning.getContent());
externalSigning.setSignature(cmsSignature);
}
(CreateSignature方法sign
)
像这样
try ( InputStream resource = getClass().getResourceAsStream("test.pdf");
OutputStream result = new FileOutputStream(new File(RESULT_FOLDER, "testSignedWithSeparatedHashing.pdf"));
PDDocument pdDocument = PDDocument.load(resource) )
{
sign(pdDocument, result, data -> signWithSeparatedHashing(data));
}
(CreateSignature测试方法testSignWithSeparatedHashing
)
生成正确签名的 PDF,至少与所讨论的证书和私钥适用于手头的任务一样正确。
一句话:
OP 使用了 IOUtils.toByteArray(content))
(我在上面的代码中也是如此)。但考虑到 OP 的开始评论
what if the PDF file is too big to be uploaded? ex: 100mb
这样做并不是一个好主意,因为它会将一个大文件一次加载到内存中,只是为了散列。如果真的想考虑一个应用程序的资源足迹,应该一次读取几 KB 的流并使用 MessageDigest.update
连续消化数据,最后只使用 MessageDigest.digest
来获得结果哈希值。