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
Android Guides Invisible digital signatures

Invisible Digital Signatures ​

An invisible digital signature displays no text, image, or logo on the page. Its signature data remains embedded in the PDF and can be viewed and verified through the ComPDF SDK or a PDF reader’s signature panel.

In Android 3.2.0, use CPDFPadesOptions.setVisible(false) to create an invisible signature with a zero-size signature field. Set the appearance type to AppearanceType.EMPTY and omit text, image, and logo settings.

Visibility affects only the page appearance. Signature integrity, certificate validity, and certificate trust still require verification.

Step 1: Prepare the signing materials

Apply a license that includes digital signature functionality, then prepare the following:

  • An input PDF saved to a local file with at least one page.
  • A PKCS#12 file (.p12 or .pfx) containing the private key, signer certificate, and required intermediate certificates, along with its password.
  • An output path different from the input path, with an existing, writable parent directory.
  • A trusted root certificate PEM file or an OpenSSL-format certificate directory for certificate chain verification.

Run the following snippets sequentially within the same method. context is an Android Context; the application supplies inputPath, outputPath, certificatePath, certificatePassword, and trustedCertPath. Perform signing and verification on a background thread.

CPDFPadesSigner.sign() reloads the document from the file and does not include unsaved changes in memory. Save all changes before signing, and check that the input file is readable and the output directory is writable.

Step 2: Read the certificate and check its validity period

getX509ByPKCS12Cert() reads certificate information from the PKCS#12 file; it does not establish trust. Check that the returned object is valid to detect an unreadable certificate or an incorrect password.

java
import com.compdfkit.core.signature.CPDFSignature;
import com.compdfkit.core.signature.CPDFX509;

CPDFX509 certificate = CPDFSignature.getX509ByPKCS12Cert(
    certificatePath, certificatePassword);
if (certificate == null || !certificate.isValid()) {
    // Check the certificate file and password.
    return;
}
if (certificate.isExpired()) {
    // The certificate is not yet valid, has expired, or has an unreadable validity period.
    return;
}
kotlin
import com.compdfkit.core.signature.CPDFSignature
import com.compdfkit.core.signature.CPDFX509

val certificate = CPDFSignature.getX509ByPKCS12Cert(
    certificatePath, certificatePassword)
if (certificate == null || !certificate.isValid) {
    // Check the certificate file and password.
    return
}
if (certificate.isExpired) {
    // The certificate is not yet valid, has expired, or has an unreadable validity period.
    return
}

The signing process loads the private key and signing identity from the same PKCS#12 file. A readable certificate within its validity period does not guarantee successful signing or establish certificate trust.

Step 3: Create an invisible signature

This example uses Profile.B to create a baseline signature without requesting a TSA timestamp or adding long-term validation materials. The signature field is on the first page and is named InvisibleSignature. If that field name is already in use, choose a unique name.

sign() creates the signature field automatically. Do not call addFormWidget() or update the signature appearance beforehand. The example disables automatic post-signing verification and explicitly verifies the output in Step 4 to distinguish file generation from the verification result.

java
import android.graphics.RectF;
import com.compdfkit.core.signature.CPDFPadesOptions;
import com.compdfkit.core.signature.CPDFPadesResult;
import com.compdfkit.core.signature.CPDFPadesSigner;

CPDFPadesOptions options = new CPDFPadesOptions()
    .setProfile(CPDFPadesOptions.Profile.B)
    .setCertificate(certificatePath, certificatePassword)
    .setFieldName("InvisibleSignature")
    .setField(0, new RectF(0, 0, 0, 0))
    .setVisible(false)
    .setAppearanceType(CPDFPadesOptions.AppearanceType.EMPTY)
    .setSignerInfo("Approval", "Shanghai", "")
    .setVerifyAfterSign(false);

CPDFPadesResult signingResult = CPDFPadesSigner.sign(
    inputPath, outputPath, null, null, options);
if (signingResult == null || !signingResult.isSuccess()) {
    // If signingResult is not null, inspect getErrors() and getWarnings().
    return;
}
String signedPath = signingResult.getFinalPath();
kotlin
import android.graphics.RectF
import com.compdfkit.core.signature.CPDFPadesOptions
import com.compdfkit.core.signature.CPDFPadesResult
import com.compdfkit.core.signature.CPDFPadesSigner

val options = CPDFPadesOptions()
    .setProfile(CPDFPadesOptions.Profile.B)
    .setCertificate(certificatePath, certificatePassword)
    .setFieldName("InvisibleSignature")
    .setField(0, RectF(0f, 0f, 0f, 0f))
    .setVisible(false)
    .setAppearanceType(CPDFPadesOptions.AppearanceType.EMPTY)
    .setSignerInfo("Approval", "Shanghai", "")
    .setVerifyAfterSign(false)

val signingResult = CPDFPadesSigner.sign(
    inputPath, outputPath, null, null, options)
if (signingResult == null || !signingResult.isSuccess) {
    // If signingResult is not null, inspect errors and warnings.
    return
}
val signedPath = signingResult.finalPath

sign() leaves the input file unchanged and cannot sign an existing signature field. setVisible(false) does not prevent subsequent editing. This API does not set a DocMDP certification reference and is not equivalent to the no-changes permission in the iOS example.

Step 4: Reopen the output and verify the signature

Open the output file at signedPath, inspect the signature information, and use CPDFSignatureValidator to verify the new signature field. Set trustedCertPath from the application’s approved trust configuration. Do not automatically trust the certificate being verified merely to obtain a successful result.

java
import com.compdfkit.core.document.CPDFDocument;
import com.compdfkit.core.signature.CPDFSignatureValidationOptions;
import com.compdfkit.core.signature.CPDFSignatureValidationResult;
import com.compdfkit.core.signature.CPDFSignatureValidator;

CPDFDocument signedDocument = new CPDFDocument(context);
try {
    if (signedDocument.open(signedPath)
            != CPDFDocument.PDFDocumentError.PDFDocumentErrorSuccess) {
        return;
    }
    CPDFSignatureValidationOptions validationOptions =
        new CPDFSignatureValidationOptions()
            .setFieldName("InvisibleSignature")
            .setTrustedCertPath(trustedCertPath);
    CPDFSignatureValidationResult validation =
        CPDFSignatureValidator.validate(signedDocument, validationOptions);
    if (validation != null && validation.isSuccess()) {
        // Check the report count, getOverallStatus(), and getItems() in each report.
    } else {
        // Verification did not execute successfully. Inspect errors and warnings.
    }
} finally {
    signedDocument.close();
}
kotlin
import com.compdfkit.core.document.CPDFDocument
import com.compdfkit.core.signature.CPDFSignatureValidationOptions
import com.compdfkit.core.signature.CPDFSignatureValidationResult
import com.compdfkit.core.signature.CPDFSignatureValidator

val signedDocument = CPDFDocument(context)
try {
    if (signedDocument.open(signedPath) !=
        CPDFDocument.PDFDocumentError.PDFDocumentErrorSuccess) {
        return
    }
    val validationOptions = CPDFSignatureValidationOptions()
        .setFieldName("InvisibleSignature")
        .setTrustedCertPath(trustedCertPath)
    val validation = CPDFSignatureValidator.validate(
        signedDocument, validationOptions)
    if (validation != null && validation.isSuccess) {
        // Check the report count, overallStatus, and items in each report.
    } else {
        // Verification did not execute successfully. Inspect errors and warnings.
    }
} finally {
    signedDocument.close()
}

validation.isSuccess() only indicates that the verification process executed successfully. Check that a report was returned for the target signature field and distinguish VALID, INVALID, INDETERMINATE, and NOT_CHECKED. Successful execution alone does not establish that the signature is valid.

Default verification checks signature integrity, coverage, document permissions, the certificate chain, and timestamps. Revocation and LTV checks are not enabled by default. Without trusted root certificates, certificate chain checks and related checks may be indeterminate. See Verify Digital Signatures for details on interpreting the results.

Inspect the result

The output PDF displays no signature appearance on its pages. Use getSignatureCount() and getPdfSignature() to read the signatures, then inspect the field name, bounds, and page with getFieldName(), getBounds(), and getPageIndex(document).

An invisible signature remains listed in the signature panel. A reader may report an unknown identity when the certificate is self-signed or the device lacks the required trust configuration. Evaluate signature integrity and certificate trust separately.

For TSA timestamps or long-term validation, see Digital Signature Timestamps and Long-Term Validation and PAdES Signing.