{"record":{"id":"267ae78d69e0452f","repo":"spring-projects/spring-security","slug":"digestauthenticationfilter-noncecompromised","errorCode":"DigestAuthenticationFilter.nonceCompromised","errorMessage":"Nonce token compromised {0}","messagePattern":"Nonce token compromised (.+?)","errorType":"exception","errorClass":"BadCredentialsException","httpStatus":401,"severity":"error","filePath":"web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java","lineNumber":410,"sourceCode":"\t\t\tString[] nonceTokens = StringUtils.delimitedListToStringArray(nonceAsPlainText, \":\");\n\t\t\tif (nonceTokens.length != 2) {\n\t\t\t\tthrow new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage(\n\t\t\t\t\t\t\"DigestAuthenticationFilter.nonceNotTwoTokens\", new Object[] { nonceAsPlainText },\n\t\t\t\t\t\t\"Nonce should have yielded two tokens but was {0}\"));\n\t\t\t}\n\t\t\t// Extract expiry time from nonce\n\t\t\ttry {\n\t\t\t\tthis.nonceExpiryTime = Long.valueOf(nonceTokens[0]);\n\t\t\t}\n\t\t\tcatch (NumberFormatException nfe) {\n\t\t\t\tthrow new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage(\n\t\t\t\t\t\t\"DigestAuthenticationFilter.nonceNotNumeric\", new Object[] { nonceAsPlainText },\n\t\t\t\t\t\t\"Nonce token should have yielded a numeric first token, but was {0}\"));\n\t\t\t}\n\t\t\t// Check signature of nonce matches this expiry time\n\t\t\tString expectedNonceSignature = DigestAuthUtils.md5Hex(this.nonceExpiryTime + \":\" + entryPointKey);\n\t\t\tif (!Utf8.isEqual(expectedNonceSignature, nonceTokens[1])) {\n\t\t\t\tthrow new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage(\n\t\t\t\t\t\t\"DigestAuthenticationFilter.nonceCompromised\", new Object[] { nonceAsPlainText },\n\t\t\t\t\t\t\"Nonce token compromised {0}\"));\n\t\t\t}\n\t\t}\n\n\t\tString calculateServerDigest(@Nullable String password, String httpMethod) {\n\t\t\t// Compute the expected response-digest (will be in hex form). Don't catch\n\t\t\t// IllegalArgumentException (already checked validity)\n\t\t\treturn DigestAuthUtils.generateDigest(DigestAuthenticationFilter.this.passwordAlreadyEncoded, this.username,\n\t\t\t\t\tthis.realm, password, httpMethod, this.uri, this.qop, this.nonce, this.nc, this.cnonce);\n\t\t}\n\n\t\tboolean isNonceExpired() {\n\t\t\tlong now = System.currentTimeMillis();\n\t\t\treturn this.nonceExpiryTime < now;\n\t\t}\n\n\t\t@Nullable String getUsername() {","sourceCodeStart":392,"sourceCodeEnd":428,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java#L392-L428","documentation":"The decoded nonce's second token must equal md5Hex(expiryTime + \":\" + entryPointKey) — the server's signature proving the nonce was issued by this entry point. validateAndDecode throws this BadCredentialsException when the signature check fails, indicating a forged, expired-regenerated, or foreign nonce (the message names the suspicious nonce).","triggerScenarios":"A client sends a nonce whose decoded signature doesn't match the server's current entryPointKey computation — nonces from a server with a different key, a nonce whose expiry portion was edited after issuance, or replayed nonces after the server restarted with a new random key.","commonSituations":"Server restarts or multiple nodes with different/mismatched DigestAuthenticationEntryPoint keys while clients cache nonces; deliberate tampering/replay attacks; environments where the key changed between deployments.","solutions":["Have the client request a fresh nonce from the current server's 401 challenge instead of replaying a cached one.","If running multiple server nodes, give every node the same DigestAuthenticationEntryPoint key (set explicitly, not left random) so all nodes validate the same nonces.","Verify no one/thing is modifying the nonce in transit (TLS, trusted proxies only).","Treat repeated occurrences as a security signal: log the nonce from the message and investigate the source for forgery attempts."],"exampleFix":"// before (per-node random key -> cross-node failures)\nDigestAuthenticationEntryPoint ep = new DigestAuthenticationEntryPoint();\n// after\nDigestAuthenticationEntryPoint ep = new DigestAuthenticationEntryPoint();\nep.setKey(\"shared-stable-key-across-nodes\"); // identical on every instance","handlingStrategy":"try-catch","validationCode":"String plain = new String(java.util.Base64.getDecoder().decode(nonce.getBytes(StandardCharsets.UTF_8)));\nString[] t = plain.split(\":\", -1);\nString expectedSig = md5Hex(Long.parseLong(t[0]) + \":\" + sharedEntryPointKey);\nif (!MessageDigest.isEqual(expectedSig.getBytes(), t[1].getBytes())) {\n    throw new IllegalStateException(\"nonce signature invalid; request a fresh challenge\");\n}\n","typeGuard":null,"tryCatchPattern":"try {\n    chain.doFilter(request, response);\n} catch (BadCredentialsException e) {\n    if (e.getMessage().startsWith(\"Nonce token compromised\")) {\n        securityAuditLog.warn(\"Possible nonce tampering/replay\", e);\n        response.sendError(401, \"Invalid nonce; re-authenticate\");\n    }\n}","preventionTips":["Set an explicit, identical DigestAuthenticationEntryPoint key on every server node","Re-authenticate from a fresh challenge rather than replaying cached nonces after restarts","Always serve and accept Authorization headers only over TLS","Monitor 'Nonce token compromised' occurrences as a potential attack signal"],"tags":["spring-security","digest-auth","nonce","tampering","security"],"backgroundTag":"checksum-mismatch","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}