import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cms.CMSSignedDataStreamGenerator;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
public class PDFBoxSignedWatermarkedExample {
private static final String KEYSTORE_PATH = "keystore.p12";
private static final char[] KEYSTORE_PASSWORD = "changeit".toCharArray();
private static final String KEY_ALIAS = "testkey";
private static final String WATERMARK_IMAGE_PATH = "watermark.png";
public static void main(String[] args) {
try {
Security.addProvider(
new org.bouncycastle.jce.provider.BouncyCastleProvider());
// Generate, watermark, and sign the PDF
ByteArrayOutputStream signedOutputStream =
generateWatermarkedAndSignedPdf();
try (FileOutputStream fos =
new FileOutputStream("signed_watermarked_test.pdf")) {
fos.write(signedOutputStream.toByteArray());
}
System.out.println("Signed and watermarked PDF created:
signed_watermarked_test.pdf");
} catch (Exception e) {
e.printStackTrace();
}
}
public static ByteArrayOutputStream generateWatermarkedAndSignedPdf()
throws Exception {
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new FileInputStream(KEYSTORE_PATH), KEYSTORE_PASSWORD);
PrivateKey privateKey = (PrivateKey) ks.getKey(KEY_ALIAS, KEYSTORE_PASSWORD);
Certificate[] certificateChain = ks.getCertificateChain(KEY_ALIAS);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PDDocument document = new PDDocument();
// Add a page with sample content
PDPage page = new PDPage(PDRectangle.A4);
document.addPage(page);
try (PDPageContentStream contentStream =
new PDPageContentStream(document, page)) {
contentStream.beginText();
contentStream.setFont(
new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD), 12);
contentStream.newLineAtOffset(100, 700);
contentStream
.showText("This is a test PDF with a digital signature,
watermark, and restricted permissions.");
contentStream.endText();
}
// Apply watermark to each page
PDImageXObject watermarkImage =
PDImageXObject.createFromFile(WATERMARK_IMAGE_PATH, document);
PDPageTree pages = document.getPages();
for (PDPage pdfPage : pages) {
PDRectangle pageSize = pdfPage.getMediaBox();
try (PDPageContentStream contentStream =
new PDPageContentStream(document, pdfPage,
PDPageContentStream.AppendMode.APPEND, true, true)) {
float x = (pageSize.getWidth() - watermarkImage.getWidth()) / 2;
float y = (pageSize.getHeight() - watermarkImage.getHeight()) / 2;
contentStream.drawImage(watermarkImage, x, y);
}
}
// Set permissions with owner password and user password
AccessPermission accessPermission = new AccessPermission();
accessPermission.setCanPrint(true);
accessPermission.setCanModify(false);
accessPermission.setCanExtractContent(true);
StandardProtectionPolicy protectionPolicy =
new StandardProtectionPolicy("ownerpassword", "userpassword",
accessPermission);
protectionPolicy.setEncryptionKeyLength(256);
document.protect(protectionPolicy);
// Save the document with encryption applied to an output stream
document.save(outputStream);
document.close();
// Sign the document
return signPdf(new ByteArrayInputStream(outputStream.toByteArray()),
privateKey, certificateChain);
}
public static ByteArrayOutputStream signPdf(InputStream pdfInputStream,
PrivateKey privateKey, Certificate[] chain)
throws Exception {
// Read all bytes from InputStream for PDFBox compatibility
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int bytesRead;
byte[] data = new byte[1024];
while ((bytesRead = pdfInputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, bytesRead);
}
buffer.flush();
byte[] pdfBytes = buffer.toByteArray();
// Load the PDF document from byte array with the user password
PDDocument document = Loader.loadPDF(pdfBytes, "userpassword");
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName("Test User");
signature.setLocation("USA");
signature.setReason("Testing Digital Signature");
signature.setSignDate(Calendar.getInstance());
document.addSignature(signature, new SignatureInterface() {
@Override
public byte[] sign(InputStream content) {
try {
return signContent(content, privateKey, chain);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
});
ByteArrayOutputStream signedOutput = new ByteArrayOutputStream();
document.saveIncremental(signedOutput);
document.close();
return signedOutput;
}
public static byte[] signContent(InputStream content, PrivateKey privateKey,
Certificate[] chain) throws Exception {
List<X509Certificate> certList = new ArrayList<>();
for (Certificate cert : chain) {
certList.add((X509Certificate) cert);
}
CMSSignedDataStreamGenerator cmsStreamGenerator =
new CMSSignedDataStreamGenerator();
cmsStreamGenerator.addCertificates(new JcaCertStore(certList));
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withRSA")
.setProvider("BC").build(privateKey);
JcaX509CertificateHolder certificateHolder =
new JcaX509CertificateHolder((X509Certificate) chain[0]);
cmsStreamGenerator.addSignerInfoGenerator(
new org.bouncycastle.cms.SignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder()
.setProvider("BC").build())
.build(contentSigner, certificateHolder));
ByteArrayOutputStream signatureOutput = new ByteArrayOutputStream();
try (OutputStream out = cmsStreamGenerator.open(signatureOutput, true)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = content.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
return signatureOutput.toByteArray();
}
}