---
title: "Como funciona a assinatura com PKI Express em Java"
description: "Como funciona a assinatura digital no projeto de exemplos do PKI Express em Java: principais arquivos de código (controller, view e módulo de JavaScript)."
canonical: "https://docs.lacunasoftware.com/articles/pki-express/java/how-it-works"
locale: "pt-BR"
product: "PKI Express"
last_updated: "2026-10-12"
---

# Como funciona a assinatura com PKI Express em Java

Este artigo explica como funciona o processo de assinatura digital realizado no projeto de exemplos do [PKI Express](https://docs.lacunasoftware.com/articles/pki-express) em Java.

## Principais arquivos de código

* Controller [PadesSignatureExpressController](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)
* View [pades-signature-express/index](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/pades-signature-express/index.html)
* Módulo de JavaScript [signature-start-form](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)
* View [pades-signature-express/start](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/pades-signature-express/start.html)
* Módulo de JavaScript [signature-complete-form](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-complete-form.js)
* View [pades-signature-express/signature-info](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/pades-signature-express/signature-info.html)

## Processo de assinatura

### 1. Usuário vai para a página de assinatura

Nesse momento, o usuário visita a URL `/pades-signature-express?fileId={fileToSign}` (`GET`). O controller `PadesSignatureExpressController` é o responsável por atender à requisição, mais especificamente no método `get()`:

```java
@Controller
@RequestMapping("/pades-signature-express")
public class PadesSignatureExpressController {

	/**
	 * GET /pades-signature-express?fileId={fileToSign}
	 *
	 * This action simply renders the page.
	 */
	@GetMapping
	public String get(@RequestParam(value = "fileId") String fileToSign) throws IOException {

		// Verify if the fileToSign exists.
		if (fileToSign == null || !StorageMock.exists(fileToSign)) {
			throw new FileNotFoundException();
		}

		// Render the signature page (templates/pades-signature-express/index.html)
		return "pades-signature-express/index";
	}
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

Nesse momento, apenas verificamos que o arquivo a ser assinado existe e renderizamos a página de assinatura, a view `pades-signature-express/index`.

> **Observação**
>
> O parâmetro `fileId` e a classe `StorageMock` são meramente detalhes do exemplo: representam a forma como a sua aplicação armazena os arquivos.

Ao final do carregamento da página, passamos referências aos elementos da página para o módulo de JavaScript `signatureStartForm`:

```js
$(document).ready(function () {
	// Once the page is ready, we call the init() function on the javascript code
	// (see signature-start-form.js).
	signatureStartForm.init({
		form: $('#signForm'),                       // The form that should be submitted when the operation is complete.
		certificateSelect: $('#certificateSelect'), // The <select> element (combo box) to list the certificates.
		refreshButton: $('#refreshButton'),         // The "refresh" button.
		signButton: $('#signButton'),               // The button that initiates the operation.
		certThumbField: $('#certThumbField'),       // The "certThumb" field.
		certContentField: $('#certContentField')    // The "certContent" field.
	});
});
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/pades-signature-express/index.html)

O módulo `signatureStartForm` bloqueia a tela e dispara a inicialização do Web PKI, passando a função `loadCertificates()` como callback para quando a inicialização
tiver sido concluída, e a função `onWebPkiError()` como `defaultFail`, o callback de erro de qualquer operação posterior do Web PKI:

```js
// ----------------------------------------------------------------------------------------------
// Initializes the signature form.
// ----------------------------------------------------------------------------------------------
function init(fe) {

	// Receive form parameters received as arguments.
	formElements = fe;

	// Wireup of buttons clicks.
	formElements.signButton.click(startSignature);
	formElements.refreshButton.click(refresh);

	// Block the UI while we get things ready.
	$.blockUI({ message: 'Initializing ...' });

	// Call the init() method on the LacunaWebPKI object, passing a callback for when the
	// component is ready to be used and another to be called when an error occurs on any of the
	// subsequent operations. For more information, see:
	// https://docs.lacunasoftware.com/en-us/articles/web-pki/get-started.html#coding-the-first-lines
	// https://webpki.lacunasoftware.com/Help/classes/LacunaWebPKI.html#method_init
	pki.init({
		ready: loadCertificates,    // As soon as the component is ready we'll load the certificates.
		defaultFail: onWebPkiError // Generic error callback (see function declaration below).
	});
}
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)

Na função `loadCertificates()`, disparamos o carregamento dos certificados disponíveis no computador do usuário, populando o elemento `<select>` da página:

```js
// ----------------------------------------------------------------------------------------------
// Function that loads the certificates, either on startup or when the user clicks the "Refresh"
// button. At this point, the UI is already blocked.
// ----------------------------------------------------------------------------------------------
function loadCertificates() {

	// Call the listCertificates() method to list the user's certificates.
	pki.listCertificates({

		// ID of the <select> element to be populated with the certificates:
		selectId: formElements.certificateSelect.attr('id'),

		// Function that will be called to get the text that should be displayed for each option:
		selectOptionFormatter: function (cert) {
			var s = cert.subjectName + ' (issued by ' + cert.issuerName + ')';
			if (new Date() > cert.validityEnd) {
				s = '[EXPIRED] ' + s;
			}
			return s;
		}

	}).success(function () {

		// Once the certificates have been listed, unblock the UI.
		$.unblockUI();

	});

}
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)

### 2. Usuário clica no botão **Sign File**

Anteriormente, o módulo de JavaScript `signatureStartForm` registrou a função de JavaScript `startSignature()` para ser chamada quando o botão **Sign File** é clicado:

```js
// Wireup of buttons clicks.
formElements.signButton.click(startSignature);
formElements.refreshButton.click(refresh);
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)

> **Observação**
>
> Note que é fundamental que o botão na página tenha `type="button"`, e não `type="submit"`, caso contrário ocorreria um *postback* imediatamente ao clicar no botão.

Portanto, quando o usuário clica no botão **Sign File**, a função `startSignature()` do `signatureStartForm` é invocada:

```js
// ----------------------------------------------------------------------------------------------
// Function called when the user clicks the "Sign File" button.
// ----------------------------------------------------------------------------------------------
function startSignature() {

	// Block the UI while we perform the signature.
	$.blockUI({ message: 'Starting signature ...' });

	// Get the thumbprint of the selected certificate.
	var selectedCertThumbprint = formElements.certificateSelect.val();

	// Get certificate content to be passed to "start" action after the form submission.
	pki.readCertificate(selectedCertThumbprint).success(/* ... */);
}
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)

Nessa função, após bloquear a página, obtemos o *thumbprint* do certificado selecionado, que será necessário em outro momento do processo de assinatura. Invocamos então a função
`readCertificate()` do Web PKI para obter a codificação do certificado escolhido (parte pública do certificado, o ".cer", sem a chave privada):

```js
// Get certificate content to be passed to "start" action after the form submission.
pki.readCertificate(selectedCertThumbprint).success(// ...
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)

Uma vez lido o certificado, preenchemos os campos ocultos da página com o *thumbprint* e o certificado e fazemos o *postback*:

```js
/* ... */.success(function (certEncoded) {

	// Fill fields needed on the next steps of the signature.
	formElements.certThumbField.val(selectedCertThumbprint);
	formElements.certContentField.val(certEncoded);

	// Submit the form.
	formElements.form.submit();

});
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-start-form.js)

### 3. Início da assinatura no *backend*

No backend, o controller `PadesSignatureExpressController` novamente é invocado para tratar o postback feito a partir do frontend com o certificado escolhido para realizar a assinatura, feito para a URL `/pades-signature-express/start` com verbo `POST`. O método `postStart()` é responsável por tratar a requisição:

```java
/**
 * POST /pades-signature-express/start?fileId={fileToSign}
 *
 * This action receives the form submission from the signature page. It will perform a PAdES
 * signature in three steps using PKI Express and Web PKI.
 */
@PostMapping("/start")
public String postStart(
	@RequestParam(value = "fileId") String fileToSign,
	@RequestParam String certThumb,
	@RequestParam String certContent,
	Model model
) throws IOException {
	// ...
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

Nesse ponto, iniciamos a assinatura usando a classe `PadesSignatureStarter`, passando a política de assinatura, o arquivo a ser assinado e a codificação do certificado escolhido para assinatura:

```java
// Get an instance of the PadesSignatureStarter class, responsible for receiving
// the signature elements and start the signature process.
PadesSignatureStarter signatureStarter = new PadesSignatureStarter();

// Set PKI default options (see Util.java)
Util.setPkiDefaults(signatureStarter);

// Set signature policy.
signatureStarter.setSignaturePolicy(StandardSignaturePolicies.PadesBasicWithLTV);

// Set PDF to be signed.
signatureStarter.setPdfToSign(StorageMock.getDataPath(fileToSign));

// Set Base64-encoded certificate's content to signature starter.
signatureStarter.setCertificateBase64(certContent);
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

A classe é criada sem argumentos e, por isso, usa o `PkiExpressConfig` padrão: o PKI Express na pasta de instalação padrão e a pasta temporária do sistema (veja [configurações da biblioteca cliente](https://docs.lacunasoftware.com/articles/pki-express/config#configurações-da-biblioteca-cliente)). O `Util.setPkiDefaults()` aplica o que todas as operações do exemplo têm em comum: as raízes confiáveis, incluindo a raiz de teste da Lacuna quando configurada ([confiando em certificados que não sejam da ICP-Brasil](https://docs.lacunasoftware.com/articles/pki-express/java/trusting-in-custom-certificates)), o modo offline e a autoridade de carimbo de tempo.

Em seguida, configuramos a aparência da assinatura:

```java
// Set visual representation. We provide a Java class that represents the visual
// representation model.
signatureStarter.setVisualRepresentation(PadesVisualElements.getVisualRepresentation());
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

> **Observação**
>
> Para mais detalhes sobre as opções de configuração da aparência da assinatura, veja a classe
> [PadesVisualElements](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/util/express/PadesVisualElements.java)
> e o artigo [representação visual de assinaturas PAdES](https://docs.lacunasoftware.com/articles/pki-express/java/visual-rep).

Por fim, concluímos a etapa inicial, obtendo os dados necessários para realizar o algoritmo de assinatura digital, e apagamos os arquivos temporários que o *starter* criou:

```java
// Start the signature process. Receive as response a SignatureStartResult instance
// containing the following fields:
// - toSignHash: The hash to be signed.
// - digestAlgorithm: The digest algorithm that will inform the Web PKI component
// to compute the signature.
// - transferFile: A temporary file to be passed to "complete" step.
SignatureStartResult result = signatureStarter.start();

// If you want to delete the temporary files created by this step use the method
// dispose(). This method MUST be called after the start() method, because it
// deletes some files needed by the method.
signatureStarter.dispose();
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

Um ponto importante: a classe `PadesSignatureStarter` salva um arquivo chamado *transfer data file* contendo informações que precisam ser recuperadas
mais à frente no processo de assinatura. Por padrão, esse arquivo é armazenado na pasta temporária do sistema, porém outra pasta pode ser configurada com o `PkiExpressConfig`
([configurações da biblioteca cliente](https://docs.lacunasoftware.com/articles/pki-express/config#configurações-da-biblioteca-cliente)). O `dispose()` não o apaga, já que a etapa de finalização precisa dele.
O método `SignatureStartResult.getTransferFile()` retorna o nome do arquivo, o qual, junto com os demais campos do resultado, é preenchido em campos ocultos na página:

```java
// Render the fields received form start() method as hidden fields to be used on
// the javascript or on the "complete" step.
model.addAttribute("certThumb", certThumb);
model.addAttribute("certContent", certContent);
model.addAttribute("toSignHash", result.getToSignHash());
model.addAttribute("transferFileId", result.getTransferFile());
model.addAttribute("digestAlgorithm", result.getDigestAlgorithm());
return "pades-signature-express/start";
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

> **Observação**
>
> Preocupações de segurança são tomadas na geração do nome desse arquivo, de modo que é impossível um usuário adivinhar o *transfer data file* de um
> processo de assinatura de outro usuário. Além disso, apenas o nome do arquivo é enviado para a página, não o caminho completo, de modo que também não é
> possível a um usuário induzir a aplicação web a ler um arquivo em uma pasta arbitrária.

O controller retorna a view `pades-signature-express/start`, que contém esses campos ocultos.

### 4. Realização do algoritmo de assinatura digital no *frontend*

Uma vez carregada a página, a função `init()` do `signatureCompleteForm` é invocada com referências aos campos ocultos:

```js
$(document).ready(function () {
	// Once the page is ready, we call the init() function on the javascript code
	// (see signature-complete-form.js).
	signatureCompleteForm.init({
		form: $('#signForm'),                             // The form that should be submitted when the operation is complete.
		certThumbField: $('#certThumbField'),             // The "certThumb" field.
		toSignHashField: $('#toSignHashField'),           // The "toSignHash" field.
		digestAlgorithmField: $('#digestAlgorithmField'), // The "digestAlgorithm" field.
		signatureField: $('#signatureField')              // The "signature" field.
	});
});
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/templates/pades-signature-express/start.html)

Bloqueamos então a página e invocamos novamente a inicialização do Web PKI, porém dessa vez passando a função `sign()` como callback:

```js
// ----------------------------------------------------------------------------------------------
// Initializes the signature form.
// ----------------------------------------------------------------------------------------------
function init(fe) {

	// Receive form parameters received as arguments.
	formElements = fe;

	// Block the UI while we get things ready.
	$.blockUI({ message: 'Signing ...' });

	// Call the init() method on the LacunaWebPKI object, passing a callback for when the
	// component is ready to be used and another to be called when an error occurs on any of the
	// subsequent operations. For more information, see:
	// https://docs.lacunasoftware.com/en-us/articles/web-pki/get-started.html#coding-the-first-lines
	// https://webpki.lacunasoftware.com/Help/classes/LacunaWebPKI.html#method_init
	pki.init({
		ready: sign,                 // As soon as the component is ready we'll perform the signature.
		defaultFail: onWebPkiError  // Generic error callback defined below.
	});
}
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-complete-form.js)

Na função `sign()`, invocamos o Web PKI para realizar o algoritmo de assinatura digital com a chave privada do certificado, usando como dados de entrada os dados retornados pelo PKI Express no backend:

```js
// ----------------------------------------------------------------------------------------------
// Function that performs the signature on startup. At this point, the UI is already blocked.
// ----------------------------------------------------------------------------------------------
function sign() {

	// Call signHash() on the Web PKI component passing the "toSignHash", the digest algorithm
	// and the certificate selected by the user.
	pki.signHash({
		thumbprint: formElements.certThumbField.val(),
		hash: formElements.toSignHashField.val(),
		digestAlgorithm: formElements.digestAlgorithmField.val()
	}).success(function (signature) {

		// Fill the "signature" field, needed on server-side to complete the signature.
		formElements.signatureField.val(signature);

		// Submit the form.
		formElements.form.submit();
	});
}
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-complete-form.js)

Uma vez finalizado o algoritmo de assinatura, preenchemos o resultado em um campo oculto na página e fazemos um novo *postback* para o backend:

```js
/* ... */.success(function (signature) {

	// Fill the "signature" field, needed on server-side to complete the signature.
	formElements.signatureField.val(signature);

	// Submit the form.
	formElements.form.submit();
});
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/resources/static/js/signature-complete-form.js)

### 5. Finalização da assinatura no *backend*

No backend, o controller `PadesSignatureExpressController` novamente é invocado para tratar o postback feito a partir do frontend com o resultado do algoritmo de assinatura digital, feito para a URL `/pades-signature-express/complete` com verbo `POST`. O método `postComplete()` é responsável por tratar a requisição:

```java
@PostMapping("/complete")
public String postComplete(
	@RequestParam(value = "fileId") String fileToSign,
	@RequestParam String transferFileId,
	@RequestParam String signature,
	Model model
) throws IOException {
	// ...
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

Obtemos uma instância da classe `SignatureFinisher` e fornecemos o arquivo sendo assinado, o nome do *transfer data file* e o resultado do algoritmo de assinatura feito no frontend com o Web PKI:

```java
// Get an instance of the SignatureFinisher class, responsible for completing the
// signature process.
SignatureFinisher signatureFinisher = new SignatureFinisher();

// Set PKI default options (see Util.java)
Util.setPkiDefaults(signatureFinisher);

// Set PDF to be signed. It's the same file we used on "start" step.
signatureFinisher.setFileToSign(StorageMock.getDataPath(fileToSign));

// Set transfer file.
signatureFinisher.setTransferFilePath(transferFileId);

// Set the signature value.
signatureFinisher.setSignature(signature);
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

> **Observação**
>
> Na versão atual do pacote `pki-express` (1.23.1), o `setTransferFilePath(String)` está obsoleto: use o `setTransferFileId()`, que recebe o mesmo valor.

Geramos um novo nome de arquivo para receber o PDF assinado:

```java
// Generate path for output file and add to signature finisher.
String outputFile = StorageMock.generateFileId("pdf");
signatureFinisher.setOutputFilePath(StorageMock.getDataPath(outputFile));
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

Finalizamos o processo de assinatura e, novamente, apagamos os arquivos temporários que o *finisher* criou:

```java
// Complete the signature process.
signatureFinisher.complete();

// If you want to delete the temporary files created by this step, use the method
// dispose(). This method MUST be called after the complete() method, because it
// deletes some files needed by the method.
signatureFinisher.dispose();
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

Após esse comando, o arquivo assinado encontra-se salvo no caminho de saída definido acima. Fica, então, a critério da sua aplicação mover esse arquivo para o local correto, seja em banco de dados, sistema de arquivos ou serviço de storage. O exemplo renderiza a view `pades-signature-express/signature-info`, com um link para baixar o arquivo assinado:

```java
// Render the signature page (templates/pades-signature-express/signature-info.html).
model.addAttribute("signedPdf", outputFile);
return "pades-signature-express/signature-info";
```

[view on source](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)

**Demonstrações e exemplos**

- Demonstração: [Sign a file with your certificate](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file)
- Sign a PDF with PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureExpressController.java)
- Sign a file with CAdES using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/CadesSignatureExpressController.java)
