我在使用itext签名pdf时遇到了签名验证失败的问题。我参考了 'itext-pdf-deferred-signing-results-in-pdf-with-invalid-signature' 进行了修改,但问题还是没有解决
public static void emptySignature(String src, String dest, String fieldname, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setVisibleSignature(new Rectangle(50, 780, 144, 780), 1, fieldname);
appearance.setCertificate(chain[0]);
ExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.signExternalContainer(appearance, external, 8192);
InputStream inp = appearance.getRangeStream();
BouncyCastleDigest digest = new BouncyCastleDigest();
byte[] hash = DigestAlgorithms.digest(inp, digest.getMessageDigest("SHA256"));
//I need to pass this hash value to a third-party system and provide them with the generated signed hash for verification.
System.out.println("Hash: " + Base64.getEncoder().encodeToString(hash));
}
public static void createSignature(String src, String dest, String fieldname, PrivateKey pk, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
ExternalSignatureContainer external = new MyExternalSignatureContainer(pk, chain);
MakeSignature.signDeferred(reader, fieldname, os, external);
}
主要
public static void main(String[] args) throws DocumentException, GeneralSecurityException, IOException {
//1.get temp empty file
emptySignature("mypdf.pdf", "temp.pdf", "", null);//before deferred signing I can not get chain? How can I use this argument?
//2.get cert from third party system
Certificate[] cert = getCert();
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new FileInputStream("aaa.p12"), "ogcio".toCharArray());
String alias = ks.aliases().nextElement();
PrivateKey pk = (PrivateKey) ks.getKey(alias, "ogcio".toCharArray());
createSignature("temp.pdf", "result.pdf", "", pk, cert);
}
我的外部签名容器
public class MyExternalSignatureContainer implements ExternalSignatureContainer {
protected PrivateKey pk;
protected Certificate[] chain;
public MyExternalSignatureContainer(PrivateKey pk, Certificate[] chain) {
this.pk = pk;
this.chain = chain;
}
public byte[] sign(InputStream is) throws GeneralSecurityException {
try {
Security.addProvider(new BouncyCastleProvider());
PrivateKeySignature signature = new PrivateKeySignature(pk, "SHA256", "BC");
String hashAlgorithm = signature.getHashAlgorithm();
BouncyCastleDigest digest = new BouncyCastleDigest();
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
byte[] hash = DigestAlgorithms.digest(is, digest.getMessageDigest(hashAlgorithm));
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, MakeSignature.CryptoStandard.CMS);
byte[] extSignature = signature.sign(sh);
sgn.setExternalDigest(extSignature, null, signature.getEncryptionAlgorithm());
return sgn.getEncodedPKCS7(hash, null, null, null, MakeSignature.CryptoStandard.CMS);
} catch (IOException ioe) {
throw new ExceptionConverter(ioe);
}
}
public void modifySigningDictionary(PdfDictionary signDic) {
//signDic.put(PdfName.FILTER, PdfName.ADOBE_PPKLITE);
//signDic.put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);
}
}
我使用上面的一些代码来做。但签名无效,并显示消息“自应用签名以来,文档已被更改或损坏”。 我的 pdf:“https://drive.google.com/file/d/1jcisJPvfMEXoLFP9Ku-p6nFXuNe7ICWr/view?usp=sharing”
现有代码无法成功签署 PDF,因为在签名之前需要签名者证书,而 iAM 智能签名服务(最终将使用该代码)仅提供证书和签名。
这种仅返回签名者证书和签名字节的签名服务通常很难用于 PDF 签名,因为大多数 CMS 签名容器生成器希望在开始组装之前能够访问所有数据(显然签名字节除外)。 -be-signed 属性并请求它们的签名。对于 PAdES PDF 签名的特殊情况,它们甚至根本无法使用,因为 PAdES 基线签名需要有关待签名属性中的签名者证书的信息。
在评论中幸运的是,iAM 智能服务还提供了返回明确配置用于 PDF 签名的完整 PKCS#7 对象(即 CMS 签名容器)的功能。如果使用此服务功能,则可以简化现有代码并使用它来使用 iText 对 PDF 进行签名,如下所示:
File origFile = new File("deferredContainerSigningOriginal.pdf");
File tempFile = new File("deferredContainerSigningTemp.pdf");
File resultFile = new File("deferredContainerSigningResult.pdf");
String fieldName = "Signature";
byte[] hash = null;
try (
OutputStream tempOutput = new FileOutputStream(tempFile)
) {
PdfReader pdfReader = new PdfReader(origFile.getPath());
hash = prepareAndHashForSigning(pdfReader, tempOutput, fieldName);
}
// execute your signature container request for the calculated hash here
byte[] signatureContainer = retrievePkcs7ContainerForHash(hash);
try (
OutputStream resultOutput = new FileOutputStream(resultFile)
) {
PdfReader pdfReader = new PdfReader(tempFile.getPath());
injectPkcs7Container(pdfReader, resultOutput, fieldName, signatureContainer);
}
使用以下辅助方法和类:
/**
* This method adds a signature field to the given PDF and sets its value signature
* dictionary. The placeholder for the signature container therein is filled with 0s.
*
* @return the hash of the signed byte ranges of the added signature.
* @see #testExternalSignatureContainer()
*/
byte[] prepareAndHashForSigning(PdfReader pdfReader, OutputStream outputStream, String fieldName) throws DocumentException, IOException, GeneralSecurityException {
PdfStamper pdfStamper = PdfStamper.createSignature(pdfReader, outputStream, (char) 0);
PdfSignatureAppearance pdfSignatureAppearance = pdfStamper.getSignatureAppearance();
pdfSignatureAppearance.setVisibleSignature(new Rectangle(50, 680, 144, 780), 1, fieldName);
ExternalSignatureContainer blankContainer = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.signExternalContainer(pdfSignatureAppearance, blankContainer, 12000);
InputStream rangeStream = pdfSignatureAppearance.getRangeStream();
BouncyCastleDigest bouncyCastleDigest = new BouncyCastleDigest();
byte[] hash = DigestAlgorithms.digest(rangeStream, bouncyCastleDigest.getMessageDigest("SHA256"));
return hash;
}
/**
* This method sets the signature container placeholder in the signature dictionary
* value of the signature field with the given name.
*
* @see #testExternalSignatureContainer()
*/
void injectPkcs7Container(PdfReader pdfReader, OutputStream outputStream, String fieldName, byte[] signatureContainer) throws DocumentException, IOException, GeneralSecurityException {
ExternalSignatureContainer injectingContainer = new InjectingSignatureContainer(signatureContainer);
MakeSignature.signDeferred(pdfReader, fieldName, outputStream, injectingContainer);
}
/**
* This is a dummy method that returns the given hash itself instead of a signature
* container for it. Obviously, it is not for production purposes.
*
* @see #testExternalSignatureContainer()
*/
byte[] retrievePkcs7ContainerForHash(byte[] hash) {
return hash;
}
/**
* This {@link ExternalSignatureContainer} implementation returns a pre-generated
* byte array in its {@link #sign(InputStream)} method.
*/
static class InjectingSignatureContainer implements ExternalSignatureContainer {
final byte[] signatureContainer;
public InjectingSignatureContainer(byte[] signatureContainer) {
this.signatureContainer = signatureContainer;
}
@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
return signatureContainer;
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
}
}
这一切与您的代码非常相似,您的代码已经实现了许多正确的概念,但由于最终不得不使用不适当的签名服务功能而受到影响。
请注意,我还调整了一些常量,特别是我将为签名容器保留的大小从 8192 增加到 12000(因为您提供的示例签名容器已经是 10462 大),并且我更改了矩形的坐标,因为您的矩形的高度0.