{"record":{"id":"e31200f9099f3d2a","repo":"digininja/DVWA","slug":"decryption-failed-e31200","errorCode":null,"errorMessage":"Decryption failed","messagePattern":"Decryption failed","errorType":"exception","errorClass":"Exception","httpStatus":526,"severity":"error","filePath":"vulnerabilities/cryptography/source/token_library_high.php","lineNumber":28,"sourceCode":"\n\tif (strlen ($iv) != 16) {\n\t\tthrow new Exception (\"IV must be 16 bytes, \" . strlen ($iv) . \" passed\");\n\t}\n\t$tag = \"\";\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;\n}\n\nfunction decrypt ($ciphertext, $iv) {\n\tif (strlen ($iv) != 16) {\n\t\tthrow new Exception (\"IV must be 16 bytes, \" . strlen ($iv) . \" passed\");\n\t}\n\t$e = openssl_decrypt($ciphertext, ALGO, KEY, OPENSSL_RAW_DATA, $iv);\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 ($debug = false) {\n\t$token = \"userid:2\";\n\n\tif ($debug) {\n\t\tprint \"Clear text token: \" . $token . \"\\n\";\n\t\tprint \"Encryption key: \" . KEY . \"\\n\";\n\t\tprint \"IV: \" . (IV) . \"\\n\";\n\t}\n\n\t$e = encrypt ($token, IV);\n\t$data = array (","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/digininja/DVWA/blob/5d5c76cced604e54462b13723f5c69af58e78748/vulnerabilities/cryptography/source/token_library_high.php#L10-L46","documentation":"openssl_decrypt returned false for aes-128-cbc with OPENSSL_RAW_DATA, key \"rainbowclimbinghigh\" and the supplied (already length-checked) IV. Because the 16-byte IV check passed, the failure is cryptographic: the raw ciphertext is not a multiple of 16 bytes, a different key encrypted it, or the decrypted PKCS#7 padding did not validate after tampering.","triggerScenarios":"Token base64 that decodes to a non-block-multiple length; re-encrypting the token with a different key; flipping bytes in the ciphertext which breaks padding on the final block; submitting ciphertext produced for aes-256-gcm; base64_decode of damaged input producing truncated bytes.","commonSituations":"Token tampering experiments (this lab's purpose); key rotation between environments; tokens generated in another language that pads the 17-character key differently than PHP's zero-padding; whitespace inside base64 values.","solutions":["Submit the originally issued token unchanged to confirm the decrypt path works before modifying anything.","Ensure the token field is base64 of raw aes-128-cbc output (a 16-byte multiple) and the iv field is the original base64 value.","Re-encrypt with the exact define('KEY', 'rainbowclimbinghigh') and OPENSSL_RAW_DATA, then append the base64 iv exactly as create_token() does.","Verify the openssl extension is loaded and dump openssl_error_string() for the concrete reason.","Note for real code: the fixed IV reused for every token here is the vulnerability being demonstrated - use random IVs in production."],"exampleFix":"// before\n$e = openssl_decrypt($ciphertext, ALGO, KEY, OPENSSL_RAW_DATA, $iv);\nif ($e === false) {\n    throw new Exception (\"Decryption failed\");\n}\n// after\n$e = openssl_decrypt($ciphertext, ALGO, KEY, OPENSSL_RAW_DATA, $iv);\nif ($e === false) {\n    while ($err = openssl_error_string()) { error_log($err); }\n    throw new Exception (\"Decryption failed\");\n}","handlingStrategy":"try-catch","validationCode":"$raw = base64_decode($data_array['token'], true);\nif ($raw === false || strlen($raw) === 0 || strlen($raw) % 16 !== 0) {\n    return json_encode(['status' => 523, 'message' => 'Token is not valid AES-CBC ciphertext']);\n}","typeGuard":"function isRawCbcCiphertext(string $raw): bool\n{\n    return strlen($raw) > 0 && strlen($raw) % 16 === 0;\n}","tryCatchPattern":"try {\n    $d = decrypt($ciphertext, $iv);\n} catch (Exception $e) {\n    error_log('openssl decrypt failed: ' . $e->getMessage());\n    while ($err = openssl_error_string()) { error_log($err); }\n    $ret = ['status' => 526, 'message' => 'Unable to decrypt token'];\n}","preventionTips":["Log openssl_error_string() on every decrypt failure - it names the real cause (key length, padding, bad decrypt).","Use strict base64 decoding and verify block-size alignment before calling openssl functions.","Pin cipher, key, and padding options in shared constants for both encrypt and decrypt paths.","In production, use authenticated modes (GCM) so tampered ciphertext fails loudly instead of relying on padding errors."],"tags":["php","openssl","aes-128-cbc","padding","cryptography","dvwa"],"backgroundTag":"openssl-decrypt-failed","analyzedSha":"5d5c76cced604e54462b13723f5c69af58e78748","analyzedAt":"2026-08-21T01:20:26.904Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}