Skip to content
DemoSampleAPI ReferenceFAQ

PDF Generation Template Editor

Open-source visual PDF generation engine with customizable templates and developer-friendly APIs.

View on GitHub

Invisible Digital Signatures ​

For SDK requirements and common imports, see Digital Signatures.

An invisible digital signature does not display signature text, an image, a logo, or any other visible signature appearance on the page. The signature information is embedded in the PDF and can be inspected and verified through a PDF reader's signature panel.

The SDK creates a standard PDF signature form field (/FT /Sig) and its Widget annotation (/Subtype /Widget). The field value /V refers to a signature dictionary containing /Contents and /ByteRange. Setting the Widget rectangle to [0, 0, 0, 0] gives it zero width and height, following the PDF specification's field and annotation representation for invisible signatures. See section 1.2 of the PDF Association's Digital Signatures technical note for this representation.

Invisibility affects the page appearance. It does not change the signature's cryptographic integrity or the requirements for trusting its certificate.

Step 1: Prepare the Java application and signing materials

Initialize ComPDFKit as described in Run a Demo before running the following Java code. Prepare:

  • A saved input PDF containing at least one page.
  • A PKCS#12 identity (.p12 or .pfx) containing the private key, signer certificate, and any required intermediate certificates, together with its password.
  • A directory of independently approved trusted certificates in PEM format for certificate-chain verification.
  • An output path different from the input path.

The following blocks form one sequential workflow inside a Java method that declares throws IOException. Use these imports:

java
import com.compdfkit.core.android.graphics.RectF;
import com.compdfkit.core.annotation.form.CPDFSignatureWidget;
import com.compdfkit.core.annotation.form.CPDFWidget;
import com.compdfkit.core.document.CPDFDocument;
import com.compdfkit.core.signature.CPDFSignature;
import com.compdfkit.core.signature.CPDFSigner;
import com.compdfkit.core.signature.CPDFX509;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

Configure the paths and password in the application:

java
File input = new File("input.pdf");
File output = new File("output/invisible-signed.pdf");
File certificateFile = new File("certs/signer.p12");
String certificatePassword = "YOUR_PKCS12_PASSWORD";
File trustDirectory = new File("certs/trusted");
String trustedPath = trustDirectory.getAbsolutePath() + File.separator;

if (!input.isFile() || !input.canRead()) {
    throw new IllegalStateException("Cannot read the input PDF");
}
if (input.getCanonicalFile().equals(output.getCanonicalFile())) {
    throw new IllegalArgumentException("Use different input and output paths");
}
if (!trustDirectory.isDirectory()) {
    throw new IllegalStateException("Prepare the trusted certificate directory");
}
Files.createDirectories(output.getAbsoluteFile().getParentFile().toPath());

Step 2: Import the certificate and check its password and validity

Import means reading the signing identity from the supplied PKCS#12 file. It does not install the identity into the operating system or make its certificate trusted.

java
if (!certificateFile.isFile() || !certificateFile.canRead()) {
    throw new IllegalStateException("Cannot read the PKCS#12 file");
}
if (!CPDFSignature.checkPKCS12Password(
        certificateFile.getAbsolutePath(), certificatePassword)) {
    throw new IllegalStateException("Cannot unlock the PKCS#12 file");
}
CPDFX509 signingCertificate = CPDFSignature.getX509ByPKCS12Cert(
        certificateFile.getAbsolutePath(), certificatePassword);
if (signingCertificate == null || !signingCertificate.isValid()) {
    throw new IllegalStateException("Cannot import the signing certificate");
}
if (signingCertificate.isExpired()) {
    throw new IllegalStateException("Certificate is outside its validity period");
}
System.out.println("Signing certificate: "
        + signingCertificate.getCertInfo().getSubject().getCommonName());

getX509ByPKCS12Cert() reads the certificate for inspection. writeSignature() below loads the private key and signing identity from the same PKCS#12 file.

Step 3: Create the invisible signature field and apply the signature

Set the Widget rectangle to zero and do not configure a visible text, image, or logo appearance. The output below forbids subsequent document modifications through its DocMDP permission setting.

java
CPDFDocument document = new CPDFDocument();
try {
    document.open(input.getAbsolutePath());
    if (!document.isValid() || document.getPageCount() == 0) {
        throw new IllegalStateException("Cannot open a PDF page for signing");
    }
    CPDFSignatureWidget widget = (CPDFSignatureWidget) document.pageAtIndex(0)
            .addFormWidget(CPDFWidget.WidgetType.Widget_SignatureFields, document);
    if (widget == null || !widget.isValid()) {
        throw new IllegalStateException("Cannot create the signature field");
    }
    widget.setFieldName("InvisibleSignature");
    widget.setRect(new RectF(0, 0, 0, 0));

    boolean signed = document.writeSignature(
            widget,
            output.getAbsolutePath(),
            certificateFile.getAbsolutePath(),
            certificatePassword,
            "Signing location",
            "Approval",
            CPDFDocument.PDFDocMdpP.PDFDocMdpPForbidAllModify);
    if (!signed) {
        throw new IllegalStateException("Invisible signature creation failed");
    }
} finally {
    document.close();
}

Step 4: Reopen the output and verify the signature

Verify the exact saved output. Check cryptographic integrity, document modification permissions, certificate validity, and certificate-chain trust separately. The trust check uses the certificate embedded in the signed PDF and the configured trust directory.

java
CPDFDocument signedDocument = new CPDFDocument();
try {
    signedDocument.open(output.getAbsolutePath());
    if (!signedDocument.isValid() || signedDocument.getSignatureCount() != 1) {
        throw new IllegalStateException("Expected one signature in the output PDF");
    }
    CPDFSignature signature = signedDocument.getPdfSignature(0);
    if (signature == null || !signature.isValid()) {
        throw new IllegalStateException("Cannot read the signature");
    }
    CPDFSigner[] signers = signature.getSignerArr();
    if (signers == null || signers.length == 0) {
        throw new IllegalStateException("The signature has no signer");
    }

    boolean integrity = signature.verify(signedDocument);
    boolean permissionsValid = signature.verifyDocument(signedDocument);
    boolean certificatesTrusted = true;
    boolean certificatesWithinValidity = true;
    for (int i = 0; i < signers.length; i++) {
        if (signers[i] == null || !signers[i].isValid()) {
            throw new IllegalStateException("Cannot read the signer");
        }
        CPDFX509 certificate = signers[i].getCert();
        if (certificate == null || !certificate.isValid()) {
            throw new IllegalStateException("Cannot read the signer certificate");
        }
        boolean withinValidity = !certificate.isExpired();
        boolean chainTrusted = certificate.verifyGetChain(trustedPath, signature);
        certificatesWithinValidity &= withinValidity;
        certificatesTrusted &= chainTrusted;
        System.out.println("Signer " + i + " certificate within validity: "
                + withinValidity);
        System.out.println("Signer " + i + " certificate chain trusted: "
                + chainTrusted);
    }
    boolean verified = integrity && permissionsValid
            && certificatesWithinValidity && certificatesTrusted;
    System.out.println("Signature integrity: " + integrity);
    System.out.println("Document permissions valid: " + permissionsValid);
    System.out.println("Basic verification passed: " + verified);
    if (!verified) {
        throw new IllegalStateException("Invisible signature verification failed");
    }
} finally {
    signedDocument.close();
}

This is basic signature verification. It does not check revocation, establish a trusted signing time, or verify an LT/LTA profile. See Long-Term Validation (LTV) and PAdES for LTV validation.

Run the sample and inspect the result

samples/SignatureTest/SignatureTest.java implements the complete workflow in signInvisibleDigitalSignature() and verifyInvisibleDigitalSignature(). From samples/SignatureTest, run ./RunTest.sh. It creates a test identity, imports it, applies the invisible signature, reopens the output, and prints each verification result. The output is samples/out/SignatureTest/digitalSignature/Invisible_Signed.pdf.

The sample explicitly trusts its generated self-signed test identity in samples/SignatureTest/trusted/invisible-demo.pem. For an existing identity, edit INVISIBLE_CERTIFICATE_PATH, INVISIBLE_CERTIFICATE_PASSWORD, and INVISIBLE_TRUST_DIRECTORY, then set TRUST_INVISIBLE_DEMO_CERTIFICATE to false and supply independently approved trust anchors. These are fixed code settings, not additional command-line arguments.

Open the output's signature panel in Acrobat to inspect the signature information. The PDF page displays no visible signature appearance. Trust configured for this Java sample does not change Acrobat's trust settings, so Acrobat may still report an unknown signer until its own trust configuration is prepared.