将 SignedHash 插入 PDF 以进行外部签名过程 - WorkingSample
Insert a SignedHash into PDF for external signing process - WorkingSample
按照电子书第 4.3.3 节“Digital Signature for PDF document”
我正在尝试创建一个工作示例,其中:
- 客户有一个 PDF 文件要签名,只有 public 证书
- 外部硬件(带有私有证书)接受一个 HASH 并且 returns 一个 SIGNED HASH
我尝试这样做,但 PDF 中的签名显示文件在签名过程后被修改。
以下代码采用原始 PDF 和 public 证书,并创建一个带有空符号和 return HASH
的临时 pdf
这个哈希从外部发送到另一个远程应用程序(那里有相应的私人证书)和return签名哈希,我读取签名哈希并将其添加到临时 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, '[=10=]');
// 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);
}
}
}
这里有一些
一些提示:
在这个不完整的 中,作者说:
"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 字节的哈希
现在从未使用过 sh 变量!
我获取哈希字节[],发送给它,但 Adobe Reader 再次说该文件已被修改。
也许我可以试试 "invisible signature" 方法。或者 "visible stamper" 在签名验证过程中没有产生影响?
或者也许我需要用带符号的字节以某种方式重新创建一个 ANS.1 结构,然后签署文档?
也许 tsa 和标志之间的时间必须相同?
如有任何帮助,我们将不胜感激。
谢谢
更新 2 - 可行的解决方案 !!!
真的真的真的非常感谢mkl的回答!
工作修复是我们需要在 PKCS#7 包中生成 signed/authenticated 属性的散列!!!在 signedAttributesHash
变量处查看原始代码
您当前的密码
您当前的代码签署了完全错误的散列。
您签署 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"));
按照电子书第 4.3.3 节“Digital Signature for PDF document” 我正在尝试创建一个工作示例,其中:
- 客户有一个 PDF 文件要签名,只有 public 证书
- 外部硬件(带有私有证书)接受一个 HASH 并且 returns 一个 SIGNED HASH
我尝试这样做,但 PDF 中的签名显示文件在签名过程后被修改。
以下代码采用原始 PDF 和 public 证书,并创建一个带有空符号和 return HASH
的临时 pdf这个哈希从外部发送到另一个远程应用程序(那里有相应的私人证书)和return签名哈希,我读取签名哈希并将其添加到临时 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, '[=10=]');
// 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);
}
}
}
这里有一些
一些提示:
在这个不完整的
"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 字节的哈希
现在从未使用过 sh 变量!
我获取哈希字节[],发送给它,但 Adobe Reader 再次说该文件已被修改。
也许我可以试试 "invisible signature" 方法。或者 "visible stamper" 在签名验证过程中没有产生影响?
或者也许我需要用带符号的字节以某种方式重新创建一个 ANS.1 结构,然后签署文档?
也许 tsa 和标志之间的时间必须相同?
如有任何帮助,我们将不胜感激。
谢谢
更新 2 - 可行的解决方案 !!!
真的真的真的非常感谢mkl的回答!
工作修复是我们需要在 PKCS#7 包中生成 signed/authenticated 属性的散列!!!在 signedAttributesHash
变量处查看原始代码
您当前的密码
您当前的代码签署了完全错误的散列。
您签署 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"));