skip to content

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%

answer

  1. the order decides what is covered
  2. signing ciphertext signs bytes, not meaning
  3. an inner signature hides who signed
  4. decrypt before verify costs you
  5. private-key work on unauthenticated input

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.

solid answer

~50 s

The order fixes what the signature covers. **Sign then encrypt** — the normal SAML construction — means the identity provider signs the `<saml:Assertion>`, then encrypts the whole signed element into a `<saml:EncryptedAssertion>`. The `<ds:Signature>` travels inside the ciphertext, and after decrypting, the receiver verifies a signature over the plaintext subject and attributes. That is a binding statement about authorship of the claims. **Encrypt then sign** — signing the enclosing `<samlp:Response>` whose child is an already-encrypted assertion — covers ciphertext. It proves the responder transmitted that blob unaltered; it says nothing about who produced the plaintext inside, and where the responder and the issuer are different parties, or the receiver trusts several issuers, those are genuinely different claims. The cost of the normal order is real: the receiver must decrypt before it can verify anything, so the decryption step runs on unauthenticated input.

code

pseudocode · 15 lines
pseudocode
function process_sign_then_encrypt(response):
    encrypted = child saml:EncryptedAssertion of response
    key       = decrypt(encrypted.xenc:EncryptedKey, our_private_key)   // unauthenticated input
    assertion = decrypt(encrypted.xenc:EncryptedData, key)              // unauthenticated input
    if not verify(assertion.ds:Signature, trusted_issuer_key):
        reject "signature over the plaintext claims did not verify"
    return assertion            // the issuer authored exactly these attributes

function process_encrypt_then_sign(response):
    if not verify(response.ds:Signature, trusted_responder_key):
        reject "responder signature did not verify"                     // rejected before any key work
    encrypted = child saml:EncryptedAssertion of response
    key       = decrypt(encrypted.xenc:EncryptedKey, our_private_key)
    assertion = decrypt(encrypted.xenc:EncryptedData, key)
    return assertion            // nothing binds these attributes to an issuer

go deeper

for a junior

Recall that in the usual SAML construction the signature sits inside the encrypted assertion, so the receiver decrypts first and verifies afterwards.

for a middle

Explain what each order leaves provable: a signature over plaintext binds the claims to the issuer, a signature over ciphertext binds only the envelope to its sender.

for a senior

Name the operational cost of the order actually used — private-key work on unauthenticated input at an endpoint anyone can post to — and what the content cipher does about it.

for a principal

Decide the posture across partners: which statement you need provable, what you expose your decryption path to, and what a cipher upgrade costs to negotiate.

## Why the order is a real question A signature and an encryption applied to the same element compose in two directions, and each leaves a different statement provable. In SAML the two constructions look like this: - **Sign then encrypt.** `<saml:Assertion>` is signed with an enveloped `<ds:Signature>`, and the resulting element is then encrypted whole into a `<saml:EncryptedAssertion>` containing `<xenc:EncryptedData>`. The signature is *inside* the ciphertext. - **Encrypt then sign.** The assertion is encrypted first, and a `<ds:Signature>` is placed on the enclosing `<samlp:Response>`, whose reference digests the response including the `<saml:EncryptedAssertion>` child. The signature is *outside* and covers ciphertext. ## What each one proves | Construction | Signature covers | Provable after processing | Not provable | |---|---|---|---| | Sign then encrypt | The plaintext assertion, including `<saml:Subject>`, `<saml:Conditions>` and every `<saml:AttributeValue>` | The issuer authored exactly these claims about this subject | Who placed the ciphertext into this particular response | | Encrypt then sign | An opaque `<xenc:CipherValue>` and the response around it | This responder assembled and sent this message unaltered | That the signing party knows, or authored, what the plaintext says | The second row is the one candidates under-read. Signing ciphertext is signing bytes you may not have produced. The signer is asserting "I sent this", not "I mean this". Where the responder and the assertion's issuer are the same party and the receiver trusts exactly one issuer, the gap is narrow in practice. It widens the moment either condition fails — a receiver that accepts assertions from several partners, or a responder relaying on behalf of another issuer — because an encrypted assertion obtained in one context can be lifted into a freshly signed response by any party whose signature the receiver accepts. The plaintext is bound to nobody. There is a second, quieter property. In sign-then-encrypt, **the signature itself is confidential**. An observer of the ciphertext cannot see which key signed it or what certificate `<ds:KeyInfo>` named, so the relayed document does not disclose the issuing party. With an outer signature, that metadata is in the clear. ## The cost of the normal order Sign-then-encrypt forces a processing order on the receiver: 1. Locate the `<saml:EncryptedAssertion>`. 2. Decrypt `<xenc:EncryptedKey>` with the private key to recover the content-encryption key. 3. Decrypt `<xenc:EncryptedData>` to recover the assertion element. 4. Only now verify the `<ds:Signature>` found inside it. 5. Only then evaluate the `<saml:Conditions>` and read the attributes. Steps 2 and 3 run on **unauthenticated input**. Anyone who can post to the receiving endpoint can make it perform private-key operations on bytes of their choosing, which is both a resource cost and an exposure: the known XML Encryption weaknesses concern exactly this, a receiver whose behaviour differs observably depending on how the decryption of attacker-chosen ciphertext failed. Deployments able to negotiate an authenticated content cipher reduce the exposure; those pinned to a CBC content cipher such as `http://www.w3.org/2001/04/xmlenc#aes128-cbc` for interoperability carry it. Encrypt-then-sign inverts the trade: the receiver can reject an unsigned or badly signed message before doing any private-key work, at the price of the weaker statement about the plaintext. ## How to answer it A strong answer does three things. It names which element the signature covers in each order — the plaintext assertion, or the ciphertext blob. It states the provable claim in each case, and resists the temptation to say either is simply "more secure". And it names the cost of the order SAML actually uses: decryption precedes authentication, so the endpoint does key operations for strangers. An answer that says only "always sign then encrypt" has repeated a rule without the reasoning, and the reasoning is the whole question. It is also worth saying what neither order provides. Neither one stops a valid, correctly signed assertion from being submitted again, and neither one tells the receiver that this assertion belongs to *this* login rather than an earlier one. Those properties come from the assertion's own conditions and from the receiver remembering what it has already accepted — a different mechanism entirely from the signature and the cipher.

  • If the responder and the issuer are the same party and the receiver trusts only that partner, does the order still matter?
    Less, but not none. The authorship gap narrows because only one party could have signed either layer. What remains is the processing order: an outer signature lets the receiver reject junk before performing private-key operations, while an inner one keeps the signer's identity hidden from anyone holding the relayed ciphertext.
  • What does an attacker gain by posting arbitrary bytes to an endpoint that decrypts before verifying?
    They make the endpoint perform private-key operations on input of their choosing and observe how it behaves. That is a resource cost and, where the content cipher is unauthenticated and failure behaviour differs observably between causes, an information channel. It is the concrete reason decrypt-before-verify is a cost, not merely an ordering detail.
  • Does either order stop the same assertion being submitted twice?
    No. A resubmitted document is bit-for-bit valid under both constructions — the signature verifies, the decryption succeeds. Bounding that comes from the assertion's own validity conditions and from the receiver remembering which assertions it has already accepted, neither of which is a property of the signature or the cipher.

saying these in an interview costs you the question

  • Says encrypt-then-sign proves who authored the plaintext claims.
  • Treats the ordering as style, with no provable difference.
  • Claims sign-then-encrypt has no cost for the receiver.
  • Thinks encrypting the assertion stops it being submitted again.
  • Asserts the signature must always be outermost to be checkable.