---
title: "Assinaturas em duas etapas - Rest PKI Core"
description: "Assine com o Rest PKI Core na sua própria página, com o Web PKI, ou com uma chave do seu servidor: o PrepareSignatureAsync dá o hash e o CompleteSignatureAsync conclui."
canonical: "https://docs.lacunasoftware.com/articles/rest-pki/core/integration/two-step-signatures"
locale: "pt-BR"
product: "Rest PKI Core"
last_updated: "2026-10-12"
---

# Assinaturas em duas etapas - Rest PKI Core

Com as [sessões de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions), o usuário assina numa página hospedada pelo [Rest PKI Core](https://docs.lacunasoftware.com/articles/rest-pki/core). Quando a assinatura
precisa acontecer em outro lugar, use a API de assinatura em duas etapas:

1. O seu servidor pede ao Rest PKI Core que **prepare** a assinatura de um arquivo com um certificado. O Rest PKI Core valida o certificado e retorna o hash
   a ser assinado e o estado da assinatura (*state*).
1. A chave privada do certificado assina o hash: no navegador do usuário, com o [Web PKI](https://docs.lacunasoftware.com/articles/web-pki), ou no seu servidor.
1. O seu servidor envia a assinatura e o estado ao Rest PKI Core, que **conclui** a assinatura e retorna o documento assinado.

Isso atende a dois casos:

* **Assinar na sua própria página**: o usuário assina com um certificado instalado no computador dele, numa página da sua aplicação, por meio do Web PKI.
  Fora do *localhost*, o Web PKI precisa de uma [licença](https://docs.lacunasoftware.com/articles/web-pki/licensing).
* **Assinar com uma chave guardada no servidor**: a sua aplicação assina com um certificado próprio, como um certificado da empresa, sem um usuário.

> **Dica**
>
> Desse ponto em diante estaremos descrevendo chamadas ao Rest PKI Core. Para conseguir acompanhar, certifique-se de que já fez os
> [Primeiros passos](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started). A chave de API da aplicação precisa de um papel que permita assinaturas, como o **Operador** (veja
> [Escolhendo os papéis](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/get-started#roles)).

## Preparando a assinatura

Em .NET, chame `IRestPkiService.PrepareSignatureAsync()` com o arquivo a assinar, o certificado do signatário e as opções da assinatura:

```cs
var fileReference = FileReference.FromFile(pdfPath, "document.pdf");
var certificateReference = CertificateReference.FromBytes(certificateContent);
var prepareSignatureResult = await restPkiService.PrepareSignatureAsync(fileReference, certificateReference, new PrepareSignatureOptions() {
	SecurityContextId = securityContextId,
});
```

* O arquivo é um `FileReference`, criado a partir de um caminho, de bytes, de um stream ou de uma URL (veja [Referenciando arquivos](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/file-referencing)).
* O certificado é um `CertificateReference`: o `FromBytes()` recebe a codificação do certificado, como a que o Web PKI lê no navegador (decodificada do
  Base64); o `FromX509Certificate2()` recebe um certificado do .NET e o `FromPem()`, uma string PEM.
* O `PrepareSignatureOptions` define o `SecurityContextId`, que decide em quais certificados confiar (se omitido, vale o contexto de segurança padrão da
  organização ou, na falta dele, o da instância); o `SignatureType` (`Pdf`, `Cms` ou `Xml`); e as opções de cada tipo, como o `PdfSignatureOptions`.

O resultado traz o hash a assinar em `ToSignHash` (`Value`, com o algoritmo de hash em `Algorithm`) e o estado da assinatura em `State`, uma string opaca
de que o Rest PKI Core precisa para concluir a assinatura. Quando o Rest PKI Core recusa o arquivo, por exemplo um PDF corrompido, o cliente .NET lança uma
`PrepareSignatureException`.

Em Java, chame `RestPkiService.startSignature()`:

```java
PrepareSignatureRequest request = new PrepareSignatureRequest();
request.setSecurityContextId(securityContextId);

FileReferenceModel file = new FileReferenceModel();
file.setContent(Files.readAllBytes(pdfPath));
file.setName("document.pdf");
request.setFile(file);

CertificateReferenceModel certificate = new CertificateReferenceModel();
certificate.setContent(Base64.getDecoder().decode(certContent));
request.setCertificate(certificate);

request.setSignatureType(SignatureTypes.PDF);

PrepareSignatureResponse result = restPkiService.startSignature(request);
if (!Boolean.TRUE.equals(result.isSuccess())) {
	// result.getFailure() diz o motivo, e result.getValidationResults() por que o certificado foi recusado
	throw new Exception("The signature could not be prepared: " + result.getFailure());
}
```

O hash a assinar é `result.getToSign().getHash()`, o algoritmo de hash é `result.getToSign().getSignatureAlgorithm().getDigestAlgorithmOid()` e o estado é
`result.getState()`.

Caso esteja chamando a API diretamente:

```text
POST {endpoint}/api/signature
{
	"file": {
		"content": "Base64EncodedBytes==",
		"name": "document.pdf"
	},
	"certificate": {
		"content": "Base64EncodedCertificate=="
	},
	"securityContextId": "...",
	"signatureType": "Pdf"
}
```

Quando `success` é `true`, a resposta traz o hash a assinar em `toSign.hash` (em Base64), o algoritmo de hash em `toSign.signatureAlgorithm.digestAlgorithmOid`,
o estado em `state` e o certificado do signatário em `certificate`. Caso contrário, `failure` diz o motivo: `PdfAlreadySigned`, `CorruptedPdf`,
`CertificateValidationFailed` (com os motivos em `validationResults`), `PasswordProtectedPdf`, `PdfAConversionFailed` ou `PdfANonCompliant`. A resposta
também repete o hash em `toSignHash`, para clientes mais antigos.

## Assinando o hash

### Na sua própria página, com o Web PKI

O certificado do usuário nunca sai do computador dele; por isso, a assinatura exige duas idas e voltas entre a página e o seu servidor:

1. Na página, liste os certificados do usuário com o `listCertificates()` do Web PKI. Quando o usuário escolher um, leia a codificação dele com o
   `readCertificate(thumbprint)` e envie-a, em Base64, ao seu servidor.
1. O seu servidor [prepara a assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/two-step-signatures#preparando-a-assinatura) com esse certificado (`CertificateReference.FromBytes()` em .NET) e devolve à página o
   hash a assinar, o algoritmo de hash e o estado.
1. A página assina o hash com o `signHash()` do Web PKI e envia a assinatura, em Base64, e o estado ao seu servidor, que
   [conclui a assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/two-step-signatures#concluindo-a-assinatura).

```javascript
pki.signHash({
	thumbprint: thumbprint,         // o certificado escolhido pelo usuário
	hash: toSignHash,               // o hash a assinar, em Base64
	digestAlgorithm: digestAlgorithm // o OID do algoritmo de hash, como 2.16.840.1.101.3.4.2.1 para SHA-256
}).success(function (signature) {
	// Envie a assinatura (em Base64) e o estado ao seu servidor
});
```

O estado é protegido pelo Rest PKI Core; por isso, você pode guardá-lo num campo oculto da página entre as duas etapas. Para o `listCertificates()`, o
`readCertificate()` e o `signHash()`, veja os [Primeiros passos](https://docs.lacunasoftware.com/articles/web-pki/get-started) do Web PKI.

### No seu servidor

Em .NET, assine o hash com a chave privada do certificado:

```cs
var certificate = new X509Certificate2(pfxPath, pfxPassword);
var certificateReference = CertificateReference.FromX509Certificate2(certificate);
var prepareSignatureResult = await restPkiService.PrepareSignatureAsync(fileReference, certificateReference, signatureOptions);

var signature = certificate.GetRSAPrivateKey().SignHash(
	prepareSignatureResult.ToSignHash.Value,
	prepareSignatureResult.ToSignHash.Algorithm.HashAlgorithmName,
	RSASignaturePadding.Pkcs1
);
```

## Concluindo a assinatura

Em .NET, chame `IRestPkiService.CompleteSignatureAsync()` com o estado e a assinatura:

```cs
var signedDocument = await restPkiService.CompleteSignatureAsync(prepareSignatureResult.State, signature);
using (var stream = await signedDocument.SignedFile.OpenReadAsync()) {
	// Armazene o arquivo assinado
}
```

O resultado é o `Document` assinado, o mesmo tipo que as [sessões de assinatura](https://docs.lacunasoftware.com/articles/rest-pki/core/integration/signature-sessions) retornam: o `SignedFile.OpenReadAsync()` lê o
arquivo assinado, e a `SignedFile.Url` é um link temporário para baixá-lo. Se a assinatura ou o certificado não passar na validação, por exemplo um
certificado revogado, o cliente .NET lança uma `ValidationException`, cujos `ValidationResults` dizem o motivo.

Em Java, chame `RestPkiService.completeSignature()`:

```java
CompleteSignatureRequestV2 request = new CompleteSignatureRequestV2();
request.setState(state);
request.setSignature(Base64.getDecoder().decode(signature));

DocumentModel signedDocument = restPkiService.completeSignature(request);
try (InputStream signedContent = restPkiService.openRead(signedDocument.getSignedFile().getUrl())) {
	// Armazene o arquivo assinado
}
```

Caso esteja chamando a API diretamente:

```text
POST {endpoint}/api/signature/completion
{
	"state": "...",
	"signature": "Base64EncodedSignature=="
}
```

A resposta é o `DocumentModel` do documento assinado, o mesmo modelo que o `GET api/documents/{id}` retorna.

## Exemplos

* .NET, na sua própria página com o Web PKI: [PadesSignatureRestCoreController.cs](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureRestCoreController.cs)
* .NET, com uma chave do servidor: [PadesServerKeyRestCoreController.cs](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesServerKeyRestCoreController.cs)
* Java, na sua própria página com o Web PKI: [PadesSignatureCoreController.java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureCoreController.java)
* Node.js, na sua própria página com o Web PKI: [pades-signature-core.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-signature-core.js)

**Demonstrações e exemplos**

- Demonstração: [Sign a file with Rest PKI Core](https://try.lacunasoftware.com/pt/pki-suite/sign-a-file-rest-pki-core)
- Sign a PDF with Rest PKI Core: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesSignatureRestCoreController.cs) · [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/PadesSignatureCoreController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/pades-signature-core.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/pades-signature-rest-core/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/pades_signature_rest_core.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/pades_signature_rest_core_controller.rb)
- Sign multiple PDFs with Rest PKI Core: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/batch-pades-signature-core.js)
- Sign a PDF with a server key using Rest PKI Core: [.NET](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/dotnet/mvc/PkiSuiteAspNetMvcSample/Controllers/PadesServerKeyRestCoreController.cs)
