skip to content

Signing and Encryption

XML-DSIG over a Response or a single Assertion, and XML-Enc for an EncryptedAssertion. Interviewers ask about signature wrapping, which makes a validator check one element and read another.

part ofFederated identityoverview, primer and where to startread it →
on this pageshow

questions

5

In a signed SAML 2.0 assertion, what does the single <ds:Reference> inside <ds:SignedInfo> actually cover?

level: middleimportance: must knowfreq 48%

answer

  1. the signature signs a small block
  2. one same-document reference, one digest
  3. URI hash-id names the ID attribute
  4. strip the signature, then canonicalise
  5. exclusive c14n so a lifted subtree matches

basics

~20 s

It covers exactly one element: the ds:Reference carries a same-document URI of the form #id naming that element's ID attribute, and the DigestValue beside it is the hash of that element and its descendants after the listed transforms have run.

solid answer

~40 s

SAML requires an **enveloped** signature containing a **single** `<ds:Reference>`, and that reference must be a same-document reference to the `ID` attribute of the element being signed — an assertion with `ID="_a1b2"` is referenced as `URI="#_a1b2"`. The digest is not taken over the raw bytes as they arrived. Two transforms run first: `http://www.w3.org/2000/09/xmldsig#enveloped-signature`, which removes the `<ds:Signature>` element itself so the digest is not chasing its own tail, and a canonicalisation transform — SAML says implementations SHOULD use exclusive canonicalisation, `http://www.w3.org/2001/10/xml-exc-c14n#` — which serialises the subtree to a defined octet stream. `<ds:DigestValue>` is the hash of that stream. `<ds:SignatureValue>` is then computed over the canonicalised `<ds:SignedInfo>`, not over the document, so the signature protects the reference and digest, and the digest protects the element.

code

xml · 22 lines
xml
<saml:Assertion ID="_a1b2c3" Version="2.0" IssueInstant="2026-09-19T10:14:02Z">
  <saml:Issuer>https://idp.research-federation.example/idp</saml:Issuer>
  <ds:Signature>
    <ds:SignedInfo>
      <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
      <ds:SignatureMethod Algorithm="...agreed with the partner..."/>
      <ds:Reference URI="#_a1b2c3">
        <ds:Transforms>
          <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
          <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
        </ds:Transforms>
        <ds:DigestMethod Algorithm="...agreed with the partner..."/>
        <ds:DigestValue>Kx9rQ2h1bmtvZmRpZ2VzdA==</ds:DigestValue>
      </ds:Reference>
    </ds:SignedInfo>
    <ds:SignatureValue>Qm9ndXNzaWduYXR1cmV2YWx1ZQ==</ds:SignatureValue>
    <ds:KeyInfo>
      <ds:X509Data><ds:X509Certificate>MIIDdDCCAlyg...</ds:X509Certificate></ds:X509Data>
    </ds:KeyInfo>
  </ds:Signature>
  <saml:Subject>...</saml:Subject>
</saml:Assertion>

go deeper

for a junior

Recall the shape: one reference, one digest, and the URI is a hash character followed by the ID attribute of the element being signed.

for a middle

Explain the two-hash chain — digest over the transformed element, signature over SignedInfo — and say what each of the two transforms is for.

for a senior

Draw the consequence: the signature reaches exactly one subtree, so state what lies outside it and how a receiver decides which element it acted on.

for a principal

Weigh the interoperability surface — algorithm agility, canonicalisation variants, partners who cannot move — against the cost of a bilateral change you cannot schedule.

## Two hashes, not one An XML signature does not sign a document. It signs a small block, `<ds:SignedInfo>`, that *names* what was hashed and *states* the hash. Reading it as a chain is the fastest way to get it right: 1. Take the element to be protected — for SAML, a `<saml:Assertion>` or a `<samlp:Response>`, identified by its `ID` attribute. 2. Run the transforms listed in `<ds:Transforms>` over it, in order. 3. Hash the resulting octets; that value goes in `<ds:DigestValue>`, inside the `<ds:Reference>`. 4. Canonicalise `<ds:SignedInfo>` using the algorithm its own `<ds:CanonicalizationMethod>` names, and sign *that*; the result is `<ds:SignatureValue>`. So `<ds:SignatureValue>` covers `<ds:SignedInfo>`, and `<ds:SignedInfo>` covers the element only indirectly, through the digest. Break either link and verification fails — but note what the structure implies: **the signature's reach is exactly one subtree, the one the URI names.** Anything outside that subtree is present in the document and unprotected. ## What SAML pins down Generic XML Signature is far more permissive than SAML allows. The profile narrows it hard: - the signature MUST be **enveloped** — the `<ds:Signature>` sits inside the element it protects, rather than alongside it or in a separate document; - it MUST contain a **single** `<ds:Reference>`, so there is never an argument about which of several digests applies; - that reference MUST be a **same-document reference** to the root element's `ID` attribute value, written `URI="#_a1b2"` against `ID="_a1b2"`. The narrowing exists because multi-reference, detached and externally referenced signatures make it genuinely hard for a receiver to state what it verified. One element, one digest, one answer. ## The two transforms and why each is there | Transform | Algorithm | Job | |---|---|---| | Enveloped signature | `http://www.w3.org/2000/09/xmldsig#enveloped-signature` | Removes the `<ds:Signature>` element from the subtree before hashing, resolving the circularity of a signature that sits inside what it signs | | Exclusive canonicalisation | `http://www.w3.org/2001/10/xml-exc-c14n#` | Produces one defined octet stream from a subtree whose serialisation may legitimately vary | The second deserves the explanation, because it is what interviewers push on. XML has many byte sequences for the same information content: attribute order, whitespace inside a tag, `<a/>` against `<a></a>`, which namespace declarations are written where, character references. A hash over raw bytes would break whenever a parser round-tripped a document. Canonicalisation defines one serialisation so both parties hash the same octets. **Exclusive** canonicalisation goes further, and that is the point for SAML specifically. Inclusive canonicalisation pulls in namespace declarations inherited from ancestors; exclusive canonicalisation includes only those the subtree actually uses. That is what lets a signed `<saml:Assertion>` be lifted out of the `<samlp:Response>` it arrived in and placed inside a different document — a very normal thing to do — and still digest to the same value, because the ancestors' declarations were never part of the hash. SAML states this as a **SHOULD**, not a MUST; the variant `http://www.w3.org/2001/10/xml-exc-c14n#WithComments` retains comments. ## What the reference does not tell you Three limits are worth stating out loud, because each is a real production failure: - **It does not say which element the application will read.** Verification selects a subtree by `ID` lookup. Consumption usually selects by position — "the first assertion in the response". When the two selections disagree, the receiver verifies one element and acts on another. - **It does not cover the rest of the document.** A signature over one `<saml:Assertion>` says nothing about a second assertion, about the enclosing `<samlp:Response>`, or about anything added beside it. - **It does not tell you the key was the right one.** `<ds:KeyInfo>` and the `<ds:X509Certificate>` it may carry are attacker-supplied in the sense that matters: a receiver that verifies against the certificate the message brought has established only that the message is self-consistent. The verification key must come from the trust the two parties established beforehand. The digest algorithm also ages. Deployments built when SHA-1 was standard still carry SHA-1 digests and signature methods; current federations use SHA-256-based algorithms, and mismatched algorithm support between two partners is a routine cause of a signature that verifies on one side and not the other.

  • Why must the enveloped-signature transform run before the digest is taken?
    Because the `<ds:Signature>` element lives inside the element being signed. Without removing it, the digest would have to cover a `<ds:DigestValue>` that depends on the digest — unsatisfiable. The transform deletes the signature subtree from the input, so the hash covers the assertion as it would be without its signature.
  • What breaks if a receiver hashes the bytes as they arrived instead of canonicalising first?
    Verification becomes fragile for legitimate documents. Any parse-and-reserialise on the path — attribute reordering, whitespace normalisation, a different empty-element form, a namespace declaration moved — changes the octets without changing the information, and the digest no longer matches. Canonicalisation defines the one serialisation both sides hash.
  • A receiver verifies against the <ds:X509Certificate> carried in <ds:KeyInfo>. What has it actually checked?
    Only that the document is internally consistent: the certificate it shipped matches the signature it shipped. An attacker supplying both passes. The verification key must come from the trust relationship established out of band with that partner, and `<ds:KeyInfo>` is at most a hint about which of those known keys was used.

saying these in an interview costs you the question

  • Says the signature covers the whole XML document.
  • Thinks <ds:SignatureValue> is computed over the assertion directly.
  • Treats canonicalisation as cosmetic formatting, not hash input.
  • Claims SAML allows several <ds:Reference> elements in one signature.
  • Trusts the certificate carried in <ds:KeyInfo> as the trust anchor.
open as a page

At a health research data-access portal, a posted SAMLResponse verifies cleanly yet admits a cohort nobody approved — how does XML signature wrapping achieve that?

level: seniorimportance: must knowfreq 55%

basics

~20 s

The genuine signed assertion is moved elsewhere in the XML tree and a forged copy is put in its place. Verification resolves the reference by ID and re-checks the moved original, while the application reads the element sitting in the expected position.

open as a page

In a SAML 2.0 <samlp:Response>, what does a <saml:EncryptedAssertion> protect that the TLS connection to the service provider does not?

level: juniorimportance: should knowfreq 34%

basics

~20 s

A saml:EncryptedAssertion holds the assertion as ciphertext the identity provider encrypted to the service provider's public encryption key, so the browser relaying the document, its history, its logs and any intermediary see no plaintext subject or attributes.

open as a page

Under Web Browser SSO with the HTTP POST binding, why is a signature over only the <samlp:Response> not enough?

level: middleimportance: should knowfreq 38%

basics

~20 s

The Web Browser SSO profile requires the assertions themselves to be signed when the HTTP POST binding is used. A response-level signature is a statement about one envelope: it does not travel with an assertion and does not stand alone.

open as a page

When an identity provider signs a SAML assertion and then encrypts it, what can the service provider prove that the reverse order cannot?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Sign-then-encrypt puts the ds:Signature inside the ciphertext, so after decryption the service provider verifies a signature over the plaintext claims and can prove the issuer authored exactly those attributes. An outer signature over ciphertext proves only who assembled the message.

open as a page