{"record":{"id":"2140ebac3c96b807","repo":"digininja/DVWA","slug":"decryption-failed-2140eb","errorCode":null,"errorMessage":"Decryption failed","messagePattern":"Decryption failed","errorType":"exception","errorClass":"Exception","httpStatus":526,"severity":"error","filePath":"vulnerabilities/cryptography/source/token_library_impossible.php","lineNumber":31,"sourceCode":"\n\t$e = openssl_encrypt($plaintext, ALGO, KEY, OPENSSL_RAW_DATA, $iv, $tag);\n\tif ($e === false) {\n\t\tthrow new Exception (\"Encryption failed\");\n\t}\n\treturn $e . $tag;\n}\n\nfunction decrypt ($ciphertext, $iv) {\n\tif (strlen ($iv) != 12) {\n\t\tthrow new Exception (\"IV must be 12 bytes, \" . strlen ($iv) . \" passed\");\n\t}\n\n    $tag = substr($ciphertext, -16);\n\t$text = substr($ciphertext, 0, -16);\n\n\t$e = openssl_decrypt($text, ALGO, KEY, OPENSSL_RAW_DATA, $iv, $tag);\n\tif ($e === false) {\n\t\tthrow new Exception (\"Decryption failed\");\n\t}\n\treturn $e;\n}\n\n// Added the debug flag so that when calling from the script\n// the function can print the data used to create the token\n\nfunction create_token () {\n\t$token = \"userid:2\";\n\t$iv = openssl_random_pseudo_bytes(12, $cstrong);\n\n\t$e = encrypt ($token, $iv);\n\t$data = array (\n\t\t\t\t\t\"token\" => base64_encode ($e),\n\t\t\t\t\t\"iv\" => base64_encode ($iv),\n\t\t\t\t);\n\treturn json_encode($data);\n}","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/digininja/DVWA/blob/5d5c76cced604e54462b13723f5c69af58e78748/vulnerabilities/cryptography/source/token_library_impossible.php#L13-L49","documentation":"openssl_decrypt returned false for aes-256-gcm. GCM is authenticated encryption: the last 16 bytes of the token are the auth tag, and decryption fails rather than returning garbage whenever tag verification fails - wrong key, wrong nonce, or any single-bit change in ciphertext or tag. At the impossible level this is by design: tampered or forged tokens cannot decrypt.","triggerScenarios":"Modifying any byte of the base64 token or iv field; reusing a 12-byte nonce from a different token; re-encrypting with a different key; truncating the token so substr($ciphertext, -16) slices the wrong bytes; submitting CBC ciphertext (which trips the IV-length check first at line 23).","commonSituations":"Token forgery attempts (the lab's exercise); key mismatch between the issuing and verifying services; nonce-reuse bugs in custom token code; non-strict base64_decode producing different bytes than encoded.","solutions":["Submit the token exactly as issued to confirm the happy path works - create_token() output must decrypt.","Treat this exception as authentication failure: reject the token and log, do not attempt to 'fix' the bytes.","Inspect openssl_error_string() - it reports the tag verification failure explicitly.","If legitimately re-encrypting, use the same KEY, aes-256-gcm, a fresh 12-byte nonce, and concatenate ciphertext . tag exactly as encrypt() does before base64-encoding."],"exampleFix":"// before\n$e = openssl_decrypt($text, ALGO, KEY, OPENSSL_RAW_DATA, $iv, $tag);\nif ($e === false) {\n    throw new Exception (\"Decryption failed\");\n}\n// after\n$e = openssl_decrypt($text, ALGO, KEY, OPENSSL_RAW_DATA, $iv, $tag);\nif ($e === false) {\n    throw new Exception(\"Decryption failed: GCM tag verification failed (tampered token or wrong key/IV)\");\n}","handlingStrategy":"try-catch","validationCode":"$iv = base64_decode($data_array['iv'], true);\n$raw = base64_decode($data_array['token'], true);\nif ($iv === false || strlen($iv) !== 12 || $raw === false || strlen($raw) <= 16) {\n    // token must be ciphertext (>= 1 byte) + 16-byte tag; reject malformed input early\n    return json_encode(['status' => 523, 'message' => 'Malformed token']);\n}","typeGuard":"function isGcmTokenShape(string $rawCiphertext, string $iv): bool\n{\n    return strlen($iv) === 12 && strlen($rawCiphertext) > 16;\n}","tryCatchPattern":"try {\n    $d = decrypt($ciphertext, $iv);\n} catch (Exception $e) {\n    // GCM failure == authentication failure: reject the token, never retry with tweaks\n    error_log('GCM verification failed: ' . $e->getMessage());\n    $ret = ['status' => 526, 'message' => 'Unable to decrypt token'];\n}","preventionTips":["Treat any GCM decrypt failure as a rejected token - do not surface the reason to the client or retry.","Generate a fresh 12-byte nonce per token and store ciphertext and 16-byte tag together exactly as encrypt() does.","Use strict base64 decoding and length checks before touching OpenSSL.","Keep the key fixed across all services that verify the token; mismatched keys fail exactly like tampering."],"tags":["php","openssl","aes-256-gcm","authentication-tag","cryptography","dvwa"],"backgroundTag":"aes-gcm-tag-mismatch","analyzedSha":"5d5c76cced604e54462b13723f5c69af58e78748","analyzedAt":"2026-08-21T01:20:26.904Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}