CAdES vs XAdES Digital Signatures in Java: The Differences That Matter When Your CA Asks for One and You've Built the Other
A digital signature is basically a wax seal with DNA: it doesn't matter if the envelope travels by train, plane, or stuffed in someone's backpack — if the recipient breaks the seal or tampers with the contents, you'll know. The problem is there are two types of wax seal in the CMS/XML world. They share the same name on the brochure ("ETSI advanced signature"), but you can't swap one for the other. And when the Certification Authority hands you back a validation error, the fix isn't in the error message — it's three steps back, in having picked the wrong format from the start.
My position is concrete: CAdES and XAdES are not variants of the same standard. They're formats built for different domains, with different data structures and different trust assumptions baked in. Choosing wrong isn't a technical inconvenience you patch later — it's a document that fails validation on the receiving end, even when the cryptographic signature itself is perfectly valid. I've seen the confusion come from the same place every time: someone reads "advanced signature" in a spec sheet and assumes that's the whole answer.
What CAdES and XAdES Actually Are — No Folklore
Before touching any code, it's worth separating what the standards actually say from what gets copy-pasted on StackOverflow threads that never mention which profile they tested against.
CAdES (CMS Advanced Electronic Signatures) extends the CMS/PKCS#7 format for advanced signatures. The reference standard is ETSI EN 319 122. CAdES produces a binary file — typically .p7s or .p7m — that can either contain the original document (enveloping signature) or reference an external file (detached signature).
XAdES (XML Advanced Electronic Signatures) extends XMLDSig for advanced signatures. It produces XML. It can wrap the original content inside the XML (enveloping), live inside the XML document it signs (enveloped), or point at the document externally (detached).
The structural difference is the one that actually bites in practice:
| Characteristic | CAdES | XAdES |
|---|---|---|
| Base format | CMS / PKCS#7 (binary) | XMLDSig (XML) |
| Typical extension |
.p7s, .p7m, .csig
|
.xades, .xml
|
| Signable document | Any binary or text | Natively XML; binaries as Base64 |
| Common profile in LATAM | Signing PDF documents, files | Electronic invoicing, XML contracts |
| ETSI reference | EN 319 122 | EN 319 132 |
This isn't a table for the sake of having a table. It's the first fork in the road when the CA asks for "an advanced signature" and doesn't say which format.
When the CA Says "Advanced Signature" and Doesn't Say Which One
Here's a common assumption worth naming directly: that ETSI compliance alone tells you a signature is valid for your use case. It doesn't — ETSI compliance says the cryptographic construction is sound, not that the receiving system knows how to parse it.
There are contexts where the format is dictated by regulation or by the receiving system, not by preference:
-
Electronic invoicing in several countries in the region defaults to XAdES because the invoice document is already XML. The receiving system expects to find a
<Signature>node inside that XML — not a.p7ssitting next to it. - Signing binary files (PDFs that skip PAdES, executables, ZIPs of records) tends to go through CAdES, because CAdES can wrap any binary without transforming it first.
- Interoperability with European systems (eIDAS, TSL): most validation profiles recognize both formats on paper, but real document-signing flows lean on CAdES or PAdES, not XAdES.
The practical move, before opening an IDE: ask the CA what format they expect in the SignedData or ds:Signature field, and what profile (B, T, LT, or LTA). That one question saves hours you'd otherwise spend debugging a signature that was never wrong — just aimed at the wrong contract.
DSS: The Library That Implements Both in Java
The European Commission's DSS library is the reference Java implementation for CAdES, XAdES, PAdES, and JAdES. It's open source (LGPL), actively maintained, and ships an official cookbook with runnable examples — which matters, because half the pain with these standards is not knowing which knob to turn.
DSS is what several European state signing systems run on in production. If your context needs interoperability with eIDAS infrastructure, DSS is close to a de facto requirement, not just a convenient library.
Add it to your project:
<!-- pom.xml -->
<dependency>
<!-- DSS CAdES module -->
<groupId>eu.europa.esig.dss</groupId>
<artifactId>dss-cades</artifactId>
<version>5.13</version>
</dependency>
<dependency>
<!-- XAdES module -->
<groupId>eu.europa.esig.dss</groupId>
<artifactId>dss-xades</artifactId>
<version>5.13</version>
</dependency>
Note: Version 5.13 is the latest stable release at the time of writing. Check the current version in the official DSS repository before you pin it in a real project.
Signing with CAdES in Java — Minimal Reproducible Example
// CAdES-B signature (baseline, no timestamp) using DSS
import eu.europa.esig.dss.cades.CAdESSignatureParameters;
import eu.europa.esig.dss.cades.signature.CAdESService;
import eu.europa.esig.dss.enumerations.DigestAlgorithm;
import eu.europa.esig.dss.enumerations.SignatureLevel;
import eu.europa.esig.dss.enumerations.SignaturePackaging;
import eu.europa.esig.dss.model.DSSDocument;
import eu.europa.esig.dss.model.FileDocument;
import eu.europa.esig.dss.model.SignatureValue;
import eu.europa.esig.dss.model.ToBeSigned;
import eu.europa.esig.dss.token.DSSPrivateKeyEntry;
import eu.europa.esig.dss.token.Pkcs12SignatureToken;
import java.io.File;
import java.io.IOException;
import java.security.KeyStore;
public class CadesSignerDemo {
public static DSSDocument signWithCAdES(
File binaryFile,
File pkcs12File,
String password
) throws IOException {
// 1. Load the PKCS#12 token with the private key
try (Pkcs12SignatureToken token = new Pkcs12SignatureToken(
pkcs12File, new KeyStore.PasswordProtection(password.toCharArray()))) {
DSSPrivateKeyEntry privateKey = token.getKeys().get(0);
// 2. Define CAdES signature parameters
CAdESSignatureParameters parameters = new CAdESSignatureParameters();
parameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_B); // B profile, no TSA
parameters.setSignaturePackaging(SignaturePackaging.DETACHED); // does not wrap the file
parameters.setDigestAlgorithm(DigestAlgorithm.SHA256);
parameters.setSigningCertificate(privateKey.getCertificate());
parameters.setCertificateChain(privateKey.getCertificateChain());
// 3. Load the document to sign
DSSDocument document = new FileDocument(binaryFile);
// 4. Compute the hash to be signed (ToBeSigned)
CAdESService service = new CAdESService(null); // null = no chain validation
ToBeSigned dataToSign = service.getDataToSign(document, parameters);
// 5. Sign with the private key
SignatureValue signatureValue = token.sign(
dataToSign, parameters.getDigestAlgorithm(), privateKey);
// 6. Build the signed document (.p7s detached)
return service.signDocument(document, parameters, signatureValue);
}
}
}
Signing with XAdES in Java — Same Flow, Different Format
// XAdES-B signature (baseline) — document is XML or any content as detached
import eu.europa.esig.dss.xades.XAdESSignatureParameters;
import eu.europa.esig.dss.xades.signature.XAdESService;
import eu.europa.esig.dss.enumerations.SignatureLevel;
import eu.europa.esig.dss.enumerations.SignaturePackaging;
public class XadesSignerDemo {
public static DSSDocument signWithXAdES(
File xmlFile,
File pkcs12File,
String password
) throws IOException {
try (Pkcs12SignatureToken token = new Pkcs12SignatureToken(
pkcs12File, new KeyStore.PasswordProtection(password.toCharArray()))) {
DSSPrivateKeyEntry privateKey = token.getKeys().get(0);
// XAdES-ENVELOPED: signature lives inside the original XML
XAdESSignatureParameters parameters = new XAdESSignatureParameters();
parameters.setSignatureLevel(SignatureLevel.XAdES_BASELINE_B);
parameters.setSignaturePackaging(SignaturePackaging.ENVELOPED); // node inside the XML
parameters.setDigestAlgorithm(DigestAlgorithm.SHA256);
parameters.setSigningCertificate(privateKey.getCertificate());
parameters.setCertificateChain(privateKey.getCertificateChain());
DSSDocument document = new FileDocument(xmlFile);
XAdESService service = new XAdESService(null);
ToBeSigned dataToSign = service.getDataToSign(document, parameters);
SignatureValue signatureValue = token.sign(
dataToSign, parameters.getDigestAlgorithm(), privateKey);
// Result is an XML with the embedded <ds:Signature> node
return service.signDocument(document, parameters, signatureValue);
}
}
}
The code skeleton is nearly identical between both. The fork happens in SignaturePackaging and in what each service actually spits out at the end: CAdES gives you a CMS binary, XAdES gives you XML with a node grafted into it.
The Most Common Validation Errors — And Why They Happen
1. Wrong Profile: B When the CA Expects LT
The Baseline-B profile carries no timestamp and no embedded revocation data. If the CA validates against LT or LTA, the document fails with something like INDETERMINATE / NO_REVOCATION_DATA. For LT you need a TSA (Timestamp Authority) wired into the service:
// Configure TSA to get CAdES-LT profile (includes ETSI timestamp)
OnlineTSPSource tspSource = new OnlineTSPSource("http://timestamp.digicert.com");
CAdESService service = new CAdESService(chainValidation);
service.setTspSource(tspSource);
// Change the level at signing time
parameters.setSignatureLevel(SignatureLevel.CAdES_BASELINE_LT);
2. CAdES Enveloping Over XML — The Silent Error
If you use SignaturePackaging.ENVELOPING in CAdES over an XML document, the XML gets swallowed as a binary blob inside the CMS envelope. A receiver expecting a <ds:Signature> node inside the XML won't find one. There's no cryptographic error — the signature checks out mathematically. The failure is semantic: the format doesn't match the contract the receiving system was built to parse. This is the specific mistake I'd flag as the most expensive one, because everything looks fine until it hits production.
3. Canonicalization in XAdES Enveloped
XAdES Enveloped needs a canonicalization transform (c14n) before hashing the content. If the XML has inconsistent namespace declarations, or the parser reorders things behind your back, the hash diverges from the original and validation fails with FAILED / HASH_FAILURE. DSS handles this automatically, but if you're building the XML by hand before handing it to DSS, don't touch the DOM tree between canonicalization and the signing step.
4. Incomplete Certificate Chain
Both CAdES and XAdES need the full certificate chain inside the envelope (signingCertificate + certificateChain). Skip the intermediate certificate and validation on the other end can fail with INDETERMINATE / NO_CERTIFICATE_CHAIN_FOUND — even though the cryptography is fine. DSS has methods for including the chain properly. Use them; don't shortcut this one.
Decision Matrix: CAdES or XAdES
Before writing a single line of code, run through this checklist:
What type of document are you signing?
- Arbitrary binary (PDF without PAdES, ZIP, image, executable) → CAdES detached or enveloping
- Native XML (tax receipt, structured contract, SOAP message) → XAdES enveloped or enveloping
What does the receiving system expect?
- A
.p7sfile separate from the document → CAdES detached - The XML with the signature inside it → XAdES enveloped
- A single file containing signature + document → CAdES enveloping or XAdES enveloping
What trust profile is required?
- Signature only (no timestamp) → Baseline-B
- Signature + timestamp → Baseline-T
- Signature + timestamp + embedded revocation data → Baseline-LT
- Long-Term Archival (for periods beyond certificate lifetime) → Baseline-LTA
Does the CA or regulation specify the format?
- If the CA gives you a spec, follow it. Your own technical analysis is useful for understanding why it works that way, not for arguing the spec into something more convenient.
What You Can't Conclude Without Your Own Experiment
A guide like this has limits worth naming out loud instead of hiding:
- I'm not claiming one profile is "more secure" than the other. Both are ETSI advanced signatures; the real security depends on algorithm implementation, key custody, and which TSA you trust.
-
The code examples pass
nullas the chain validator. In a real validation flow you need aCertificateVerifierwired to actual CRL/OCSP sources. That piece depends entirely on the specific CA's infrastructure — there's no generic snippet for it. - DSS behavior shifts between versions. Check the official cookbook for the version you're pinning. The API isn't stable across minor releases.
-
Not every receiving system validates the same way. A document that passes in
dss-demo-webappdoesn't guarantee it passes on the CA's actual system if they've layered custom validation on top.
FAQ
Can I use CAdES to sign an XML?
Yes, technically. CAdES can sign any binary, XML included. The problem is semantic: if the receiving system expects XAdES with a <ds:Signature> node inside the XML itself, an external .p7s won't satisfy that contract — even if the signature is cryptographically flawless.
Does DSS support both formats with the same PKCS#12 token?
Yes. DSS's Pkcs12SignatureToken doesn't care about the target format. The same private key works for CAdES, XAdES, PAdES, or JAdES. What changes is the SignatureParameters and the Service class you build around it.
What's the practical difference between CAdES-B and CAdES-LT for validation?
CAdES-B only carries the signature and the signing certificate. CAdES-LT adds an ETSI timestamp plus revocation data (CRL or OCSP) embedded right in the CMS envelope. That lets you validate the document later, even after the certificate has expired or the CA is unreachable at verification time.
What does "detached" vs "enveloping" mean in practice?
In a detached signature, the original document stays untouched: the signature lives in a separate file. In an enveloping signature, the original document gets encapsulated inside the signature envelope itself. For files other systems need to keep processing untouched, detached is the safer default.
Why does validation pass in my code but fail at the CA's system?
Usually one of four things: (1) wrong profile than expected (B vs LT), (2) incomplete certificate chain in the envelope, (3) the receiving system runs custom validation logic that doesn't cover every standard extension, (4) bad canonicalization in XAdES. First diagnostic step, every time: run the document through the DSS reference validator before you send it anywhere.
Does XAdES enveloped modify the original XML?
Yes. XAdES enveloped injects a <ds:Signature> node into the original XML tree. If the document has a strict XSD schema that doesn't account for that node, the signature can break the XML's structural validation. In those cases, XAdES detached or enveloping are the safer picks.
The Right Question to Ask Before the First getDataToSign()
It's not "which format is better?" It's "what does the receiving system expect, and in what profile?"
CAdES and XAdES solve the same cryptographic problem — an ETSI-compliant advanced signature — for two different document worlds. CAdES grew out of CMS/PKCS#7, a world where any binary blob is welcome as-is. XAdES grew out of XML, where the signature has to sit inside the same tree as the content it protects. Mixing the two doesn't break the math. It breaks the contract with whoever's on the other end of the connection, and that's the failure mode that actually costs you hours.
My practical recommendation: before opening the IDE, get the exact profile from the CA (format + baseline level), check what type of document you're actually signing, and wire the DSS validator to real CRL/OCSP sources instead of null. The signing code is the easy part. What eats the time is figuring out what the other side of the connection is actually built to read.
If you're building a broader integration where the signature is just one layer — alongside authentication, logging, or caching — the posts on Web Crypto API in browser vs Node.js and digital identity architecture give you more context on how these pieces fit into a larger system.
Sources:
- European Commission DSS library: https://ec.europa.eu/digital-building-blocks/sites/display/DIGITAL/Digital+Signature+Service+-++DSS
- ETSI EN 319 122 – CAdES standard: https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf
This article was originally published on juanchi.dev
Top comments (0)