laravel/framework · error · DecryptException

Could not decrypt the data.

Error message

Could not decrypt the data.

What it means

decrypt() throws DecryptException('Could not decrypt the data.') when openssl_decrypt() ultimately returns false — i.e. neither the current nor any previous key successfully decrypts the ciphertext. Also thrown by ensureTagIsValid() when an AEAD tag is the wrong length (16 bytes required). Distinguish from [256]: this fires when the MAC (if checked) was OK but decryption still failed, or for AEAD ciphers where the tag/auth failed.

Source

Thrown at src/Illuminate/Encryption/Encrypter.php:201

            );

            if ($decrypted !== false) {
                break;
            }
        }

        if ($this->shouldValidateMac() && $validKey === null) {
            throw new DecryptException('The MAC is invalid.');
        }

        if ($this->shouldValidateMac()) {
            $decrypted = \openssl_decrypt(
                $payload['value'], strtolower($this->cipher), $validKey, 0, $iv, $tag ?? ''
            );
        }

        if (($decrypted ?? false) === false) {
            throw new DecryptException('Could not decrypt the data.');
        }

        return $unserialize ? unserialize($decrypted) : $decrypted;
    }

    /**
     * Decrypt the given string without unserialization.
     *
     * @param  string  $payload
     * @return string
     *
     * @throws \Illuminate\Contracts\Encryption\DecryptException
     */
    public function decryptString($payload)
    {
        return $this->decrypt($payload, false);
    }

View on GitHub (pinned to bd6b5437e6)

Solutions

  1. Verify the payload is transmitted byte-exact: use rawurlencode/rawurldecode for URLs, store as BLOB/VARBINARY not as lossy VARCHAR without a proper collation.
  2. Confirm cipher hasn't changed since the value was encrypted; re-encrypt existing data after cipher migrations.
  3. For AEAD, ensure the tag field is present and 16 bytes after base64_decode; ensureTagIsValid enforces this.
  4. Log the payload length and base64-decodability to isolate corruption from key mismatch.

Example fix

// before — encrypted value stored in a charset-lossy column
// migrations: $table->string('token');  // collation truncates bytes

// after — store as binary
$table->binary('token');
// and round-trip via the encrypter untouched:
$stored = encrypt($value);
$back = decrypt($stored);
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure byte-exact transport and storage
$raw = $request->input('token');
if (is_string($raw) && \Illuminate\Encryption\Encrypter::appearsEncrypted($raw)) {
    return decrypt($raw);
}

Try / catch

try {
    $value = decrypt($payload);
} catch (\Illuminate\Contracts\Encryption\DecryptException $e) {
    // log length/base64 validity; treat as corrupted/untrusted
    logger()->warning('decrypt failed', ['len' => strlen($payload ?? '')]);
    $value = null;
}

Prevention

When it happens

Trigger: Corrupted ciphertext/IV/tag in the payload; truncated base64; payload encrypted with an algorithm different from the current cipher; AEAD tag missing or wrong length; an encrypted value passed through a transport that mangled bytes (URL encoding, charset conversion).

Common situations: Cookies mangled by proxies/CDNs; encrypted query params URL-decoded incorrectly; switching from CBC to GCM (or vice versa) without re-encrypting; truncated database columns holding encrypted values; copy-paste truncation of long base64.

Related errors


AI-assisted analysis of laravel/framework@bd6b5437e6 (2026-08-06). Data as JSON: /data/errors/b66acdda3ebbd951.json. Report an issue: GitHub.