---
title: "Sign with CAdES - PKI Express"
description: "How to sign any file with CAdES (CMS, .p7s) in PKI Express: the two-step signature with Web PKI, the server-key signature, co-signing, detached signatures, policies and merging CMS files."
canonical: "https://docs.lacunasoftware.com/en-us/articles/pki-express/cades-signature"
locale: "en"
product: "PKI Express"
last_updated: "2026-10-13"
---

# Sign with CAdES - PKI Express

[PKI Express](https://docs.lacunasoftware.com/en-us/articles/pki-express) signs any file with CAdES, producing a CMS file (`.p7s`) on your server: in two steps with the user's certificate
and [Web PKI](https://docs.lacunasoftware.com/en-us/articles/web-pki), or in one step with a key held by the server. It also co-signs a `.p7s` and merges CMS files that sign
the same content. 
The `start-cades`, `complete-sig` and `sign-cades` commands exist since PKI Express 1.0.0; `merge-cms` since 1.9.0. 

The code is Python, from the [PKI Suite samples](https://github.com/LacunaSoftware/PkiSuiteSamples), the only language with samples for the
three operations; it uses `pkiexpress` (1.10.4). 

## Flow with the user's certificate

The flow is the one of the [PDF signature](https://docs.lacunasoftware.com/en-us/articles/pki-express/pades-signature#flow), with `CadesSignatureStarter` in place of `PadesSignatureStarter`:

1. **Browser**: Web PKI reads the user's certificate; the page posts its content.
1. **Server**: `CadesSignatureStarter.start()` returns the hash to sign, its digest algorithm and a transfer file.
1. **Browser**: Web PKI signs the hash with `signHash()`.
1. **Server**: `SignatureFinisher.complete()` writes the `.p7s`.

## 1. Start the signature

```python
signature_starter = CadesSignatureStarter()
set_pki_defaults(signature_starter)
signature_starter.signature_policy = \
    standard_signature_policies.PKI_BRAZIL_CADES_ADR_BASICA
signature_starter.set_file_to_sign_from_path(
    os.path.join(current_app.config['APPDATA_FOLDER'], file_id))
signature_starter.set_certificate_from_base64(cert_content)
signature_starter.encapsulated_content = True
response = signature_starter.start()
# response['toSignHash'], response['digestAlgorithm'], response['transferFile']
```

* `cert_content` is the user's certificate, Base64-encoded, as Web PKI's `readCertificate()` returns it. 
* `encapsulated_content` (default `True`) puts the signed file inside the `.p7s`. Set it to `False` for a detached signature, which holds
  only the signature. 
* `set_commitment_type()` adds a commitment type, with PKI Express 1.20.0 or later. 

## 2. Sign the hash in the browser

The page signs `toSignHash` with Web PKI's `signHash()` and posts the result, as in the [PDF signature](https://docs.lacunasoftware.com/en-us/articles/pki-express/pades-signature#sign-hash). 

## 3. Complete the signature

```python
signature_finisher = SignatureFinisher()
set_pki_defaults(signature_finisher)
signature_finisher.set_file_to_sign_from_path(
    os.path.join(current_app.config['APPDATA_FOLDER'], file_id))
signature_finisher.set_transfer_file_from_path(transfer_file_id)
signature_finisher.signature = signature
create_app_data()  # Guarantees that "app data" folder exists.
output_file = '%s.p7s' % (str(uuid.uuid4()))
signature_finisher.output_file = \
    os.path.join(current_app.config['APPDATA_FOLDER'], output_file)
signer_cert = signature_finisher.complete(get_cert=True)
```

Give `SignatureFinisher` the same file as in the first step; `complete(get_cert=True)` returns the signer's certificate. 

## Co-sign a CMS file

To add a signature to an existing `.p7s`, sign the `.p7s` itself: the sample's "Co-sign with another certificate" link opens the same page
with the signed file as the file to sign. 

* If that `.p7s` is detached, give the signed content as well, with `set_data_file_from_path` (`--data-file`). 
  TODO(docs): no sample co-signs a detached signature.

## Sign with a server key

`CadesSigner` signs in one call, with no browser; the sample uses a test PKCS #12 file with password `1234`. 

```python
signer = CadesSigner()
set_pki_defaults(signer)
signer.signature_policy = \
    standard_signature_policies.PKI_BRAZIL_CADES_ADR_BASICA
signer.set_file_to_sign_from_path(file_path)
signer.set_pkcs12_from_path(join(current_app.static_folder,
                                 'Pierre de Fermat.pfx'))
signer.cert_password = '1234'
signer.encapsulated_content = True
create_app_data()  # Guarantees that "app_data" folder exists.
output_file = '%s.p7s' % (str(uuid.uuid4()))
signer.output_file = join(current_app.config['APPDATA_FOLDER'], output_file)
signer_cert = signer.sign(get_cert=True)
```

The other key sources (a certificate thumbprint, a key name, a cloud certificate) are the same as for PDFs: see
[Sign with a server key](https://docs.lacunasoftware.com/en-us/articles/pki-express/pades-signature#server-key). 

## Signature policies

| Constant (Python, Node.js) | Value | Notes |
|---|---|---|
| `CADES_BES` | `cades` | |
| `CADES_BES_WITH_REVOCATION_VALUES` | `cades-rv` | |
| `CADES_T` | `cades-t` | needs a timestamp authority |
| `PKI_BRAZIL_CADES_ADR_BASICA` | `adrb` | ICP-Brasil |
| `PKI_BRAZIL_CADES_ADR_BASICA_WITH_REVOCATION_VALUE` | `adrb-rv` | ICP-Brasil |
| `PKI_BRAZIL_CADES_ADR_TEMPO` | `adrt` | ICP-Brasil; needs a timestamp authority |
| `PKI_BRAZIL_CADES_ADR_COMPLETA` | `adrc` | ICP-Brasil; needs a timestamp authority |

* ICP-Brasil AD-RA (`adra`, `PKI_BRAZIL_CADES_ADR_ARQUIVAMENTO` in Node.js, `PkiBrazilCadesAdrArquivamento` in Java) needs PKI Express 1.26.0
  or later and a timestamp authority. 
* For the timestamp authority, see [Timestamp a PDF](https://docs.lacunasoftware.com/en-us/articles/pki-express/timestamp-pdf#timestamp-authority).

## Merge CMS files

`CadesSignatureEditor` joins CMS files that sign the same content into one. Give it the signed content too when the signatures are detached. 

```python
signature_editor = CadesSignatureEditor()
set_pki_defaults(signature_editor)
create_app_data()
output_file = '%s.p7s' % (str(uuid.uuid4()))
signature_editor.set_data_file_from_path(os.path.join(current_app.config['APPDATA_FOLDER'], data_file_id))
signature_editor.add_cms_file_from_path(os.path.join(current_app.config['APPDATA_FOLDER'], file_id1))
signature_editor.add_cms_file_from_path(os.path.join(current_app.config['APPDATA_FOLDER'], file_id2))
signature_editor.output_file = os.path.join(os.path.join(current_app.config['APPDATA_FOLDER'], output_file))
signature_editor.merge()
```

* With a single CMS file and the data file, `merge()` turns a detached signature into one with the content inside. PKI Express 1.17.0 added
  merging a single file; the Python library asks for 1.18. 
* `encapsulate_content` (default `True`) decides whether the merged file holds the content. 

## In each language

| Library | Two steps (`CadesSignatureStarter`) | Server key (`CadesSigner`) | Merge (`CadesSignatureEditor`) |
|---|---|---|---|
| Node.js | yes; `setFileToSignFromPath`, `encapsulatedContent` | yes | yes |
| Java | yes; `setFileToSign`, `setEncapsulateContent` | yes | yes |
| PHP | yes; `setFileToSignFromPath`, `encapsulateContent` | yes | no |
| Python | yes; `set_file_to_sign_from_path`, `encapsulated_content` | yes | yes |
| Ruby | yes | no | no |

The samples follow the same coverage: Ruby has only the two-step sample, PHP has no merge sample. 

## Batches

The CAdES batch sample works as the [PDF batch](https://docs.lacunasoftware.com/en-us/articles/pki-express/pades-signature#batch): the page calls the start and complete steps for each file, and
Web PKI signs every hash with the same certificate. 

## Errors

A failed command ends with a PKI Express return code, such as 14 (`ValidationFailed`) or 32 (`TimestampOptionsRequired`); in Python, the
library raises an exception with the output in its message. See [Return codes](https://docs.lacunasoftware.com/en-us/articles/pki-express/return-codes). 

## See also

* [Sign a PDF (PAdES)](https://docs.lacunasoftware.com/en-us/articles/pki-express/pades-signature) and [Sign an XML document](https://docs.lacunasoftware.com/en-us/articles/pki-express/xml-signature)
* [Validate signatures](https://docs.lacunasoftware.com/en-us/articles/pki-express/validate-signatures#cades), to open a `.p7s` and extract its content
* [Printer-friendly version](https://docs.lacunasoftware.com/en-us/articles/pki-express/printer-friendly-version#cades), a PDF that lists the signers of a `.p7s`
* [PKI Express quick reference](https://docs.lacunasoftware.com/en-us/articles/pki-express/quick-reference)

**Demos and samples**

- Live demo: [Sign a file with your certificate](https://try.lacunasoftware.com/en/pki-suite/sign-a-file)
- 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) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/cades-signature-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-signature-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/cades_signature_express.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/cades_signature_express_controller.rb)
- Sign multiple files with CAdES using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/BatchCadesSignatureExpressControler.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/batch-cades-signature-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/batch-cades-signature-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/batch_cades_express.py) · [Ruby](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/ruby/rails/app/controllers/batch_cades_express_controller.rb)
- Sign a file with CAdES and a server key using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/cades-server-key-express.js) · [PHP](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/php/plain/public/cades-server-key-express/index.php) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/cades_server_key_express.py)
- Merge CMS/CAdES signatures using PKI Express: [Java](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/java/springmvc/src/main/java/com/lacunasoftware/pkisuite/controller/MergeCadesExpressController.java) · [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/merge-cades-express.js) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/merge_cades_express.py)
