PDFBox 2.x如何检测PDF文档签名后是否被篡改?
Hey there! Let's break down why PDFBox's ShowSignature.java isn't catching tampering from Adobe Illustrator, and how to fix it for your ETSI.CAdES.detached signatures. The core issue lies in how different tools modify signed PDFs and PDFBox's default validation behavior.
First, let's clarify the behavior you're seeing:
- When modifying with PDFBox, you're altering the signed byte range directly—both Adobe Reader and PDFBox catch this easily because it breaks the core cryptographic hash.
- Adobe Illustrator might be modifying elements in "unsigned" incremental update areas, but Adobe Reader checks stricter structural integrity rules that
ShowSignature.javadoesn't enforce by default.
Step 1: Validate the Full CAdES Signature Chain
The default ShowSignature.java only checks basic cryptographic validity of the signature itself. For ETSI.CAdES.detached signatures, you need to verify the message digest attribute—this ensures the actual document content matches what was signed. Here's how to extend the validation with BouncyCastle (required for CMS/CAdES operations):
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureValidator; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.ValidationResult; import org.bouncycastle.cms.*; import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; import org.bouncycastle.asn1.cms.CMSAttributes; import org.bouncycastle.asn1.DEROctetString; import java.io.File; import java.security.cert.X509Certificate; import java.util.Iterator; public class EnhancedSignatureChecker { public static void main(String[] args) throws Exception { PDDocument document = PDDocument.load(new File("tampered-document.pdf")); PDSignature signature = document.getLastSignature(); // Basic signature validation SignatureValidator validator = new SignatureValidator(signature); ValidationResult basicResult = validator.validate(); if (basicResult.getStatus() != ValidationResult.Status.VALID) { System.out.println("Basic signature validation failed: " + basicResult.getStatus()); } // Validate CAdES message digest (critical for tampering detection) CMSProcessableByteArray signedContent = new CMSProcessableByteArray(signature.getSignedContent()); CMSSignedData cmsData = new CMSSignedData(signedContent, signature.getContents()); SignerInformationStore signerStore = cmsData.getSignerInfos(); Iterator<SignerInformation> signerIter = signerStore.getSigners().iterator(); while (signerIter.hasNext()) { SignerInformation signer = signerIter.next(); X509Certificate cert = (X509Certificate) cmsData.getCertificates().getMatches(signer.getSID()).iterator().next(); // Verify the message digest matches the document content if (!signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) { System.out.println("Tampering detected: Document content doesn't match signed digest"); } } document.close(); } }
Step 2: Verify the Signed Byte Range Integrity
Illustrator might modify parts of the PDF outside the initial signed byte range but still break the signature's intent. To replicate Adobe's strict check, compare the hash of the actual signed byte range to the hash stored in the signature:
import java.nio.file.Files; import java.security.MessageDigest; import java.util.Arrays; // Add this inside the main method after retrieving the signature byte[] fullDocumentBytes = Files.readAllBytes(new File("tampered-document.pdf").toPath()); int[] byteRange = signature.getByteRange(); // Extract the exact bytes that were signed byte[] signedBytes = new byte[byteRange[2] + byteRange[3]]; System.arraycopy(fullDocumentBytes, byteRange[0], signedBytes, 0, byteRange[2]); System.arraycopy(fullDocumentBytes, byteRange[2], signedBytes, byteRange[2], byteRange[3]); // Calculate hash of the extracted bytes MessageDigest digest = MessageDigest.getInstance(signature.getDigestAlgorithm()); byte[] calculatedHash = digest.digest(signedBytes); // Extract the stored hash from the CAdES attributes DEROctetString storedDigest = (DEROctetString) signer.getSignedAttributes().get(CMSAttributes.messageDigest) .getAttrValues().getObjectAt(0); byte[] storedHash = storedDigest.getOctets(); if (!Arrays.equals(calculatedHash, storedHash)) { System.out.println("Tampering detected: Byte range hash mismatch"); }
Step 3: Check Document Structural Integrity
Adobe Reader also validates the PDF's cross-reference table, object streams, and revision history. To cover these cases:
- Use PDFBox's
PDFParserto check if objects in the signed revision have been modified or deleted - Validate that incremental updates don't alter the signed document's logical structure
Why This Works
The default ShowSignature.java is a minimal example—it only checks if the signature is cryptographically valid, not if the document's content matches what was signed. By validating the CAdES message digest and byte range consistency, you'll catch tampering from tools like Illustrator, matching Adobe Reader's stricter checks.
内容的提问来源于stack exchange,提问作者Anders Andrén

