gpt4 book ai didi

java - 将 SignedHash 插入 PDF 以进行外部签名过程 - WorkingSample

转载 作者:行者123 更新时间:2023-11-30 10:04:33 25 4
gpt4 key购买 nike

按照电子书第 4.3.3 节“Digital Signature for PDF document”我正在尝试创建一个工作示例,其中:

  • 客户有一个要签名的 PDF,只有一个公共(public)证书
  • 外部硬件(带有私有(private)证书)获取一个哈希值并返回一个签名的哈希值

我尝试这样做,但 PDF 中的签名显示文件在签名过程后被修改。

以下代码获取原始 PDF 和公共(public)证书并创建一个带有空符号的临时 pdf 并返回 HASH

这个哈希从外部发送到另一个远程应用程序(那里有相应的私有(private)证书)并返回签名哈希,我读取签名哈希并将其添加到临时 pdf 中。

完整的工作代码已更新:

package com.Marloo;


import org.apache.commons.codec.Charsets;
import org.bouncycastle.util.encoders.Base64;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.security.*;

import java.io.*;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.*;

public class Test {

public static final String CERT = "src/main/resources/certificate.pem";
public static final String SRC = "src/main/resources/tmp.pdf";
public static final String DEST = "src/main/resources/signed.pdf";

public static void main(String args[]) throws IOException {
getHash(SRC, CERT);
}



public static void getHash(String doc, String cert) throws IOException {

try {

File initialFile = new File(cert);
InputStream is = new FileInputStream(initialFile);

// We get the self-signed certificate from the client
CertificateFactory factory = CertificateFactory.getInstance("X.509");
Certificate[] chain = new Certificate[1];
chain[0] = factory.generateCertificate(is);

// we create a reader and a stamper
PdfReader reader = new PdfReader(doc);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfStamper stamper = PdfStamper.createSignature(reader, baos, '\0');

// we create the signature appearance
PdfSignatureAppearance sap = stamper.getSignatureAppearance();
sap.setReason("TEST REASON");
sap.setLocation("TEST LOCATION");
//sap.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig"); //visible
sap.setVisibleSignature(new Rectangle(36, 748, 36, 748), 1, "sig"); //invisible
sap.setCertificate(chain[0]);

// we create the signature infrastructure
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.setReason(sap.getReason());
dic.setLocation(sap.getLocation());
dic.setContact(sap.getContact());
dic.setDate(new PdfDate(sap.getSignDate()));
sap.setCryptoDictionary(dic);
HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
exc.put(PdfName.CONTENTS, new Integer(8192 * 2 + 2));
sap.preClose(exc);
ExternalDigest externalDigest = new ExternalDigest() {
public MessageDigest getMessageDigest(String hashAlgorithm)
throws GeneralSecurityException {
return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
}
};
PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", null, externalDigest, false);
InputStream data = sap.getRangeStream();
byte hash[] = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));


// we get OCSP and CRL for the cert
OCSPVerifier ocspVerifier = new OCSPVerifier(null, null);
OcspClient ocspClient = new OcspClientBouncyCastle(ocspVerifier);
byte[] ocsp = null;
if (chain.length >= 2 && ocspClient != null) {
ocsp = ocspClient.getEncoded((X509Certificate) chain[0], (X509Certificate) chain[1], null);
}

byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, MakeSignature.CryptoStandard.CMS);
InputStream sh_is = new ByteArrayInputStream(sh);
byte[] signedAttributesHash = DigestAlgorithms.digest(sh_is, externalDigest.getMessageDigest("SHA256"));


System.out.println("----------------------------------------------");
System.out.println("Hash to be sign:");
System.out.println( new String(Base64.encode(signedAttributesHash), Charsets.UTF_8));
System.out.println("----------------------------------------------");
System.out.println("Insert b64 signed hash [ENTER]");
System.out.println("----------------------------------------------");

Scanner in = new Scanner(System.in);
String signedHashB64 = in.nextLine();
System.out.println( signedHashB64);

ByteArrayOutputStream os = baos;

byte[] signedHash = org.apache.commons.codec.binary.Base64.decodeBase64(signedHashB64.getBytes());

// we complete the PDF signing process
sgn.setExternalDigest(signedHash, null, "RSA");
Collection<byte[]> crlBytes = null;
TSAClientBouncyCastle tsaClient = new TSAClientBouncyCastle("http://timestamp.gdca.com.cn/tsa", null, null);

byte[] encodedSig = sgn.getEncodedPKCS7(hash, tsaClient, ocsp, crlBytes, MakeSignature.CryptoStandard.CMS);
byte[] paddedSig = new byte[8192];
System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
PdfDictionary dic2 = new PdfDictionary();
dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));

try {
sap.close(dic2);
} catch (DocumentException e) {
throw new IOException(e);
}

FileOutputStream fos = new FileOutputStream(new File(DEST));
os.writeTo(fos);

System.out.println("pdfsig " + System.getProperty("user.dir") + "/" + DEST);
System.out.println("------------------End Of Life --------------------------");

System.exit(0);


} catch (GeneralSecurityException e) {
throw new IOException(e);
} catch (DocumentException e) {
throw new IOException(e);
}

}


}

这里有一些screenshot

一些提示:在这个不完整的post作者说:

"After much debugging, we finally found the problem.

For some mysterious reason, the method that generates the hash of the document, was executed twice, invalidating the first hash (which we use to send to the service).

After a refactoring work of the code, the original code worked correctly.

Very thanks to all people that help me, especially mkl."

但没有提供进一步的信息,压模上写的时间和 TSA 的时间也故意不同。我想这不会是问题所在。

一些提示?

谢谢

更新1

(更新了之前的代码)

外部服务不接受整个 Sign 结构的输入,只接受 32 字节的哈希

screenshot2

现在 sh var 从未被使用过!

我将哈希字节 [] 发送给它,但 Adob​​e Reader 再次说该文件已被修改。

也许我可以尝试使用“隐形签名”方法。或者“可见压模”在签名验证过程中没有产生影响?

或者也许我需要用带符号的字节以某种方式重新创建一个 ANS.1 结构,然后签署文档?

也许 tsa 和标志之间的时间必须相同?

Screenshot3

我们将不胜感激任何形式的帮助。

谢谢

更新 2 - 可行的解决方案!!!

真的真的真的非常感谢mkl的回答!

工作修复是我们需要在 PKCS#7 包内生成已签名/已验证属性的哈希值!!!在 signedAttributesHash 变量中查看原始代码 signed pdf image

最佳答案

您当前的代码

您当前的代码签署了完全错误的散列。

你签署hash,它被计算为

InputStream data = sap.getRangeStream();
byte hash[] = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));

即您直接签署文档已签名范围的哈希值。这是错误的,因为您正在构建具有签名属性的 PKCS#7 签名容器,即文档签名范围的哈希值必须是这些签名属性之一的值,并且您必须对签名属性的哈希值进行签名!

你之前的代码

您以前的代码也签署了错误的字节,但更接近正确的字节。

你曾经签署 last32 计算为

byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, ocsp, null, MakeSignature.CryptoStandard.CMS);
byte[] last32 = Arrays.copyOfRange(sh, sh.length - 32, sh.length);

即您正确地生成了带有 hash 作为属性值的签名属性字节(又名经过身份验证的属性字节),但是您只是获取了其中的最后 32 个字节。这是错误的,您必须对已签名属性字节的哈希值进行签名,而不是它们的最后 32 个字节。

应该做什么

您应该签署 signedAttributesHash,即签名属性字节的哈希,即

byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, ocsp, null, MakeSignature.CryptoStandard.CMS);
byte[] signedAttributesHash = DigestAlgorithms.digest(new ByteArrayInputStream(sh), externalDigest.getMessageDigest("SHA256"));

关于java - 将 SignedHash 插入 PDF 以进行外部签名过程 - WorkingSample,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55901977/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com