In the WS-Security UsernameToken profile, how does PasswordDigest differ from PasswordText, and what stops a captured token from being replayed?
answer
- two Password Type URIs
- Base64 of a SHA-1
- nonce, created, then the secret
- freshness window plus nonce cache
- both sides hold the secret
basics
~20 sPasswordText sends the password, or an equivalent, as is; PasswordDigest sends Base64(SHA-1(nonce + created + password)). Replay is stopped by the receiver, which rejects stale Created times and caches used nonces for that freshness window.
solid answer
~50 sIn the **Username Token Profile 1.1.1**, `wsse:Password` has a `Type` attribute: `#PasswordText`, the default, carries the password or an equivalent in the clear, and `#PasswordDigest` carries `Base64(SHA-1(nonce + created + password))`, built from a fresh random `wsse:Nonce` and a `wsu:Created` time. The digest by itself does not stop replay, because a captured token can be resent byte for byte. The receiver stops it: the profile RECOMMENDS rejecting tokens without both nonce and created, rejecting stale `Created` values (five minutes is its guideline minimum), and caching used nonces for at least that window. The catch is that the receiver recomputes the digest, so it must hold the password or an agreed password equivalent, and whatever it holds then works as a password for anyone who steals it. The profile also RECOMMENDS sending the Password element only over a secure transport or inside an encrypted token, and warns that a captured message still lets an attacker guess a weak password offline.
code
pseudocode · 11 lineson receive UsernameToken(user, type, digest, nonceB64, created):
if type != PasswordDigest: apply PasswordText policy and stop
if nonceB64 is missing or created is missing: reject
if now - created > FRESHNESS or created > now + SKEW: reject
nonce = base64Decode(nonceB64)
if nonceCache.contains(user, nonce): reject
secret = credentials.lookup(user)
expected = base64(sha1(nonce + utf8(created) + utf8(secret)))
if not constantTimeEquals(expected, digest): reject
nonceCache.put((user, nonce), expiresAfter = FRESHNESS)
acceptgo deeper
Recall the two Password types and that the digest mixes a nonce, a creation time and the password, then hashes and Base64-encodes them.
Explain the exact digest formula, the three recommended receiver checks, and why the creation time is what keeps the nonce cache bounded.
Show the production consequences: credential storage the digest forces, replay across clusters that share no cache, and why TLS or encryption is still recommended.
Judge whether a partner's demand for PasswordDigest is worth weakening credential storage, against PasswordText over TLS or a certificate-signed message instead.
## The token and its two password types The **Username Token Profile 1.1.1** (OASIS, 2012) describes how a client identifies itself with a user name inside the `wsse:Security` header and, optionally, proves it with a password or *password equivalent* (a derived password, a one-time password, or a stored hash the server treats as the secret). The element looks like this: ```xml <wsse:UsernameToken wsu:Id="UT-1"> <wsse:Username>claims-portal</wsse:Username> <wsse:Password Type="...#PasswordDigest">weYI3nXd8LjMNVksCKFV8t3rgHh3Rw==</wsse:Password> <wsse:Nonce EncodingType="...#Base64Binary">WScqanjCEAC4mQoBE07sAQ==</wsse:Nonce> <wsu:Created>2026-10-01T09:14:05Z</wsu:Created> </wsse:UsernameToken> ``` | `Type` URI | What `wsse:Password` holds | When the profile expects it | |---|---|---| | `#PasswordText` (default) | the password, a hash or a derived value, as is | hashed equivalents that use no nonce or created time, or a digest other than SHA-1 | | `#PasswordDigest` | a digest of nonce, created time and password | the algorithm the profile defines | `#PasswordText` means only that the value is "in the clear" rather than a digest of it. ## The digest, step by step Whichever of `wsse:Nonce` and `wsu:Created` is present MUST be included in the digest, in this order: 1. Take the **nonce** as the octets of its decoded value (Base64 by default). 2. Take the **created** timestamp as the UTF-8 octets of the element's text. 3. Append the **password** (or shared secret, or equivalent). 4. Hash the concatenation with **SHA-1** and **Base64**-encode the result. The secret goes **last** on purpose. SHA-1's output is its full internal state, so if the secret came first an attacker could append blocks and compute a valid hash for a longer input; ending with the password means any extension must end with a value the attacker does not know. Each message that carries a nonce MUST use a new one. ## Why the digest alone does not stop replay An attacker who records the token can resend it unchanged, and the digest still verifies. The profile therefore RECOMMENDS three countermeasures for the receiver: - **Require both** a nonce and a creation time; reject tokens missing either. - **Enforce freshness**: reject a token whose `Created` is stale. Five minutes is given as a guideline minimum. - **Cache used nonces** for at least the freshness period and reject any nonce already in the cache. The creation time is what keeps the cache finite: a token older than the window is rejected as stale anyway, so its nonce never needs to be remembered longer. The profile is explicit that these measures do **not** cover replay to a *different* receiver, such as a second cluster that does not share the nonce cache. Binding the user name, a domain or the intended receiver into the hash is offered as a non-normative idea that needs a separately agreed password type. ## What the digest forces a server to store To verify, the receiver recomputes the same digest, so it needs the same secret the client used. The profile says so directly: PasswordDigest can only be used if the plain-text password or a password equivalent is available to **both** sides. That has consequences: - A server that stores only a **salted, deliberately slow password hash** cannot verify a digest of the raw password. - If the parties agree to use a stored hash as the "password", that hash *becomes* the secret: anyone who steals it can authenticate without ever learning the original password. - PasswordText, sent over a protected channel, lets the server keep a one-way verifier and compare a hash of what arrives. ## What the digest does not protect 1. **Eavesdroppers.** A captured nonce, created time and digest let an attacker test password guesses offline; the profile says the password must still be strong enough to resist that, and RECOMMENDS sending `wsse:Password` only over a secure transport or with the token encrypted. 2. **The message.** A UsernameToken authenticates a user name; it says nothing about whether the Body was altered. Integrity needs a signature over the Body and the token. 3. **The plain digest.** Without nonce and created, the profile notes the digest offers no real additional security over PasswordText unless the channel is secured or the token encrypted. A separate section of the profile derives keys from a password using `wsse11:Salt` and `wsse11:Iteration` (default 1000); when that is used, the password MUST NOT appear in the token at all.
- Why does the PasswordDigest formula put the password last rather than first?SHA-1's output is its complete internal state after the input. If the secret were not last, an attacker holding only the hash could append further blocks and compute a valid hash for a longer input. With the password at the end, any extension must finish with a value the attacker does not know. The profile makes the same point about nonce and created: placed last, an attacker could append a new creation time.
- Do a fresh nonce and Created stop a token being replayed to a different service that shares the same password?No. The profile says its countermeasures do not cover replay to a different receiver, for example two clusters in one security domain that do not share a nonce cache. It lists non-normative options (hashing in the user name, a domain or the intended receiver), but each needs a separately agreed password type between the parties.
saying these in an interview costs you the question
- PasswordDigest makes the token safe to send over plain HTTP.
- The nonce alone prevents replay, so the server needs no nonce cache.
- PasswordDigest lets the server store a verifier that is useless to a thief.
- The digest hashes the password first and the nonce last.
- A UsernameToken proves the Body was not changed in transit.