I encountered the problem of signature verification failure when using itext to sign pdf. I took it as a reference 'itext-pdf-deferred-signing-results-in-pdf-with-invalid-signature' Modifications were made, but the problem was still not solved
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);
}
main
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);
}
MyExternalSignatureContainer
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);
}
}
I used some code above to do. But the signature is invalid with the message "The document has been altered or corrupted since the signatures was applied." my pdf:"https://drive.google.com/file/d/1jcisJPvfMEXoLFP9Ku-p6nFXuNe7ICWr/view?usp=sharing"
The existing code couldn't successfully sign the PDF as it needed the signer certificate before signing while the iAM Smart signing service (for which the code eventually shall be used) provides the certificate only together with the signature.
Such signing services that return the signer certificate only together with the signature bytes are difficult to use for PDF signatures in general because most CMS signature container generators expect to have access to all data (except obviously the signature bytes) before starting to assemble the to-be-signed attributes and requesting a signature for them. For the special case of PAdES PDF signatures they even cannot be used at all as PAdES baseline signatures require information about the signer certificate in the to-be-signed attributes.
In comments it fortunately turned out that the iAM Smart service also provides the functionality of returning a full PKCS#7 object (i.e. CMS signature container) explicitly profiled for PDF signing. If one uses this service functionality, one can simplify the existing code and use it to sign PDFs with iText as follows:
using the following helper methods and class:
(CreateDeferredSignature test)
This all is pretty similar to your code which already implemented a number of correct concepts but which suffered from the background of eventually having to use an inappropriate signing service functionality.
Beware, I additionally adapted some constants, in particular I increased the size reserved for the signature container from your 8192 to 12000 (because the example signature container you provided already is 10462 large) and I changed the coordinates of the rectangle as yours had height 0.