digininja/DVWA · error · Exception
Decryption failed
Error message
Decryption failed
What it means
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.
Source
Thrown at vulnerabilities/cryptography/source/token_library_impossible.php:31
$e = openssl_encrypt($plaintext, ALGO, KEY, OPENSSL_RAW_DATA, $iv, $tag);
if ($e === false) {
throw new Exception ("Encryption failed");
}
return $e . $tag;
}
function decrypt ($ciphertext, $iv) {
if (strlen ($iv) != 12) {
throw new Exception ("IV must be 12 bytes, " . strlen ($iv) . " passed");
}
$tag = substr($ciphertext, -16);
$text = substr($ciphertext, 0, -16);
$e = openssl_decrypt($text, ALGO, KEY, OPENSSL_RAW_DATA, $iv, $tag);
if ($e === false) {
throw new Exception ("Decryption failed");
}
return $e;
}
// Added the debug flag so that when calling from the script
// the function can print the data used to create the token
function create_token () {
$token = "userid:2";
$iv = openssl_random_pseudo_bytes(12, $cstrong);
$e = encrypt ($token, $iv);
$data = array (
"token" => base64_encode ($e),
"iv" => base64_encode ($iv),
);
return json_encode($data);
}View on GitHub (pinned to 5d5c76cced)
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.
Example fix
// before
$e = openssl_decrypt($text, ALGO, KEY, OPENSSL_RAW_DATA, $iv, $tag);
if ($e === false) {
throw new Exception ("Decryption failed");
}
// after
$e = openssl_decrypt($text, ALGO, KEY, OPENSSL_RAW_DATA, $iv, $tag);
if ($e === false) {
throw new Exception("Decryption failed: GCM tag verification failed (tampered token or wrong key/IV)");
} Defensive patterns
Strategy: try-catch
Validate before calling
$iv = base64_decode($data_array['iv'], true);
$raw = base64_decode($data_array['token'], true);
if ($iv === false || strlen($iv) !== 12 || $raw === false || strlen($raw) <= 16) {
// token must be ciphertext (>= 1 byte) + 16-byte tag; reject malformed input early
return json_encode(['status' => 523, 'message' => 'Malformed token']);
} Type guard
function isGcmTokenShape(string $rawCiphertext, string $iv): bool
{
return strlen($iv) === 12 && strlen($rawCiphertext) > 16;
} Try / catch
try {
$d = decrypt($ciphertext, $iv);
} catch (Exception $e) {
// GCM failure == authentication failure: reject the token, never retry with tweaks
error_log('GCM verification failed: ' . $e->getMessage());
$ret = ['status' => 526, 'message' => 'Unable to decrypt token'];
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- IV must be 12 bytes, {strlen($iv)} passed
- Decryption failed
- IV must be 16 bytes, {strlen($iv)} passed
- Decryption failed
- Could not decode JSON object.
AI-assisted analysis of digininja/DVWA@5d5c76cced (2026-08-21).
Data as JSON: /api/errors/2140ebac3c96b807.
Report an issue: GitHub.