{"record":{"id":"56ad45ba027e0dd8","repo":"jwtk/jjwt","slug":"ciphertext-decryption-failed-authentication-tag-v","errorCode":null,"errorMessage":"Ciphertext decryption failed: Authentication tag verification failed.","messagePattern":"Ciphertext decryption failed: Authentication tag verification failed\\.","errorType":"exception","errorClass":"io.jsonwebtoken.security.SignatureException","httpStatus":null,"severity":"error","filePath":"impl/src/main/java/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithm.java","lineNumber":200,"sourceCode":"        InputStream in = Assert.notNull(req.getPayload(),\n                \"Decryption request content (ciphertext) InputStream cannot be null.\");\n        final InputStream aad = req.getAssociatedData(); // can be null if there's no associated data\n        final byte[] tag = assertTag(req.getDigest());\n        final byte[] iv = assertDecryptionIv(req);\n        final AlgorithmParameterSpec ivSpec = getIvSpec(iv);\n\n        // Assert that the aad + iv + ciphertext provided, when signed, equals the tag provided,\n        // thereby verifying none of it has been tampered with:\n        byte[] aadBytes = aad == null ? Bytes.EMPTY : Streams.bytes(aad, \"Unable to read AAD bytes.\");\n        byte[] digest;\n        try {\n            digest = sign(aadBytes, iv, in, macKeyBytes);\n        } finally {\n            Bytes.clear(macKeyBytes);\n        }\n        if (!MessageDigest.isEqual(digest, tag)) { //constant time comparison to avoid side-channel attacks\n            String msg = \"Ciphertext decryption failed: Authentication tag verification failed.\";\n            throw new SignatureException(msg);\n        }\n        Streams.reset(in); // rewind for decryption\n\n        final InputStream ciphertext = in;\n        jca(req).withCipher(new CheckedFunction<Cipher, byte[]>() {\n            @Override\n            public byte[] apply(Cipher cipher) throws Exception {\n                cipher.init(Cipher.DECRYPT_MODE, decryptionKey, ivSpec);\n                withCipher(cipher, ciphertext, plaintext);\n                return Bytes.EMPTY;\n            }\n        });\n    }\n}\n","sourceCodeStart":182,"sourceCodeEnd":215,"githubUrl":"https://github.com/jwtk/jjwt/blob/fb71496164c71442d08adec4571d9616ed5e1b8d/impl/src/main/java/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithm.java#L182-L215","documentation":"HmacAesAeadAlgorithm (A128GCM/A192GCM/A256GCM JWE content encryption) authenticates ciphertext with an HMAC-derived tag; on decrypt it recomputes the tag and compares in constant time. A mismatch means the ciphertext, IV, AAD, or tag was altered or the key is wrong, so decryption fails fast with a SignatureException instead of returning garbage plaintext.","triggerScenarios":"Decrypting a JWE whose authentication tag doesn't verify: wrong decryption key, tampered or truncated compact token, ciphertext/IV/tag fields corrupted during transport, or re-encoding the token with a different character set.","commonSituations":"Storing tokens in systems that mangle base64url (e.g. leading '=' padding added); shared secrets differing between encryptor and decryptor (env-specific keys); attempting to decrypt with an older rotated key; malicious modification of the token.","solutions":["Verify both sides use the identical secret key (compare key bytes/IDs out-of-band).","Inspect the token end-to-end transport for truncation or re-encoding; base64url must remain intact.","Re-encrypt and resend the token if corruption in transit is suspected — do not attempt to repair.","If this happens unexpectedly with attacker-controlled input, treat it as tampering and audit the source."],"exampleFix":"// before: decrypting with a stale rotated key\nJwe<Claims> jwe = Jwts.parser().decryptWith(oldSecretKey).build().parseEncryptedClaims(token);\n// after\nSecretKey currentKey = loadCurrentKey();\nJwe<Claims> jwe = Jwts.parser().decryptWith(currentKey).build().parseEncryptedClaims(token);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  Jwe<Claims> jwe = Jwts.parser().decryptWith(secretKey).build().parseEncryptedClaims(token);\n} catch (SignatureException e) {\n  // authentication tag mismatch: key wrong or token tampered/corrupted\n  auditLog.recordTamperAttempt(token);\n}","preventionTips":["Keep encryption/decryption keys in sync (same key store, rotation plan)","Transmit tokens in base64url-safe channels; never re-encode","Treat every tag failure as possible tampering and log it","Prefer AAD-protected tokens produced and parsed by the same library version"],"tags":["jwe","aead","gcm","authentication-tag","tampering"],"backgroundTag":"authentication-tag-mismatch","analyzedSha":"fb71496164c71442d08adec4571d9616ed5e1b8d","analyzedAt":"2026-09-09T00:33:09.982Z","contentChangedAt":"2026-09-09T00:33:09.982Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}