Digital signing on Android (2025)
Table of contents
You may have read about digital signatures on our blog in the past. This post revisits the topic with special attention to the Android side of things.
Here’s a quick reminder of what a digital signature is in the context of a PDF:
- It lets you verify that a document wasn’t modified.
- It lets you know who authored a document.
If you want more in-depth knowledge about the signing part, read our blog on how to digitally sign PDF documents.
Use Nutrient’s Android SDK to integrate digital signatures, streamline workflows, and secure your documents.
Signing on Android
With that out of the way, here’s a look at the signing options on Android using SigningManager:
- Direct signing with private key — This allows you to provide a
PrivateKey(opens in a new tab) that will be used directly for signing. - Custom signing with certificates — This allows you to provide certificates and sign the byte array yourself, which is useful for external signing services.
- Third-party signing service — This allows you to take full control of the signing process by retrieving unsigned data and embedding a PKCS#7 signature container.
These options can be split into two categories, which are outlined below.
Simple signature creation
Using SigningManager.signDocument() with a private key lets Nutrient do the heavy lifting of creating a signature. All your application has to provide is the private key. Use this signing option for straightforward scenarios when you have direct access to the signing key. For advanced use cases that require custom signing logic or external signing services, you’ll want to use the contained signature creation approach described below.
Contained signature creation
The SigningManager provides methods for contained signature creation, which is split into three steps:
- The PDF needs to be prepared for signing. This includes embedding the signature graphic, as well as reserving space in the PDF where the signature data can be stored. The
getDataToSign()method handles this preparation and returns the unsigned data. - The unsigned data can now be signed. Here you have full control over how the signing will work — you can do it in your app, use some hardware that’s connected to the device, or even call a web service to sign the data. This step will create a PKCS#7 structure that contains the signature.
- The PKCS#7 signature structure created in step 2 is now embedded using
embedPKCS7Signature(), and the PDF is digitally signed.
Now that you know the available options, you can start putting them to use. The simple signature creation scenario is already documented well in our digital signatures guide, so the next section will look more closely at the contained signature creation.
Leverage Nutrient’s AI-powered document tools and APIs to automate document workflows and secure signatures.
Contained signatures
As discussed before, the contained signature creation is split into three parts.
Preparing the document and retrieving data to sign
Before you can sign with an external service, you need to retrieve the unsigned data from the document. The SigningManager provides the getDataToSign() method to handle this. This method prepares the document for signing and returns the data that needs to be signed:
// Build signer options with the signature form field and output file.val signerOptions = SignerOptions.Builder(signatureFormField, outputFileUri) .build()
// Step 1 — Retrieve the unsigned data from the document.val unsignedDataResponse = SigningManager.getDataToSign(context, signerOptions)if (unsignedDataResponse.isSuccess) { // The unsigned data is now ready to be signed. val (dataToSign, hashAlgorithm) = unsignedDataResponse.getOrThrow()
// Step 2 — Sign the data. // Step 3 — Embed the signature.}This takes care of the first step. The getDataToSign() method prepares the PDF and returns a pair containing the unsigned byte array and the hash algorithm to use for signing.
Creating the PKCS#7 signature
Now that you have the unsigned data, you can move on to the next step: actually creating a digital signature. Depending on your exact requirements, doing this ranges from fairly simple all the way to quite complicated. Here’s how you’d sign the data:
// Step 2 — Sign the data with your signing service.// This is where you have full control over the signing process.// You can sign it locally in your app, use hardware connected to the device,// or call an external web service.
val signedData: ByteArray = when { // Option 1: Sign locally using your private key. useLocalSigning -> { signDataLocally(dataToSign, hashAlgorithm, privateKey) }
// Option 2: Sign using an external service. useExternalService -> { // Call your external signing service externalSigningService.sign(dataToSign, hashAlgorithm) }
else -> throw IllegalStateException("No signing method configured")}Here’s what a local signing implementation might look like:
private fun signDataLocally( dataToSign: ByteArray, hashAlgorithm: HashAlgorithm, privateKey: PrivateKey): ByteArray { // The `dataToSign` contains all the bytes of the document that need to be covered by the signature. // To be more specific, these are the bytes before and after the signature form field where we // want to place the signature.
// Hash the data using the specified algorithm. val messageDigest = MessageDigest.getInstance(hashAlgorithm.name) val hash = messageDigest.digest(dataToSign)
// Sign the hashed data using your private key. val signature = Signature.getInstance("SHA256withRSA") signature.initSign(privateKey) signature.update(hash) val signedHash = signature.sign()
// Encode the signature in PKCS#7 format (DER encoding). // This typically requires a library like Bouncy Castle. return encodeToPKCS7(signedHash, certificates)}For external signing services, the process is similar, but you send the data to the service and receive back a PKCS#7-encoded signature.
With the signed PKCS#7 data ready, you can now embed it into the document.
Embedding the signature
Now that you have the signed PKCS#7 data, you can embed it into the document using SigningManager.embedPKCS7Signature():
// Step 3 — Embed the PKCS#7 signature into the document.val response = SigningManager.embedPKCS7Signature(context, signerOptions, signedData)if (response.isSuccess) { // The document was successfully signed! val signedDocument = outputFileUri} else { // Handle embedding errors here. val error = response.exceptionOrNull()}The embedPKCS7Signature() method takes the signed byte array and embeds it into the prepared document. Internally, it prepares the signature dictionary that’s written to the PDF. This dictionary includes important metadata such as which OS was used for signing and what format the signature has.
Now you have a digitally signed document.
Where to go from here
This post covered how the SigningManager API handles contained signatures, and it hopefully gave you a better idea of how digital signatures work in general. If you want to see more examples of signing with external services, check out our third-party signing example(opens in a new tab). Digital signatures are a complicated topic, so this guide should give you a better idea of how they work and where to use them.
Get started with Nutrient’s APIs and SDKs today and implement digital signatures in your Android apps quickly.