laravel/framework · error · DecryptException
The MAC is invalid.
Error message
The MAC is invalid.
What it means
During decrypt() with a non-AEAD cipher (CBC), Laravel validates the HMAC-MAC against the current and all previous keys. If none of the keys produces a matching MAC, $validKey stays null and DecryptException('The MAC is invalid.') is thrown. This is the canonical integrity/tamper-detection failure and almost always means the payload was encrypted with a different key (or tampered with).
Source
Thrown at src/Illuminate/Encryption/Encrypter.php:191
if ($validMac && $validKey === null) {
$validKey = $key;
}
continue;
}
$decrypted = \openssl_decrypt(
$payload['value'], strtolower($this->cipher), $key, 0, $iv, $tag ?? ''
);
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.
*View on GitHub (pinned to bd6b5437e6)
Solutions
- Register the old key for decryption: call Encrypter::previousKeys([old_keys]) or set APP_PREVIOUS_KEYS in config, so legacy payloads can still be decrypted.
- If rotation was intentional and old data is disposable, clear the affected cookies/sessions/queues so clients get fresh payloads.
- Verify APP_KEY is identical across all servers/containers sharing the encrypted data.
- Confirm the cipher hasn't changed (cipher change invalidates all MACs even with the same key material).
Example fix
// before — key rotated, legacy payloads fail
// .env: APP_KEY=base64:NEW...
// after — register previous keys for graceful decryption
// config/app.php
'previous_keys' => [
env('APP_PREVIOUS_KEYS'),
],
// .env
APP_KEY=base64:NEW...
APP_PREVIOUS_KEYS=base64:OLD... Defensive patterns
Strategy: try-catch
Validate before calling
// Register previous keys so legacy CBC payloads still decrypt
// config/app.php: 'previous_keys' => [env('APP_PREVIOUS_KEYS')]
// then verify each registered key's length matches the cipher Try / catch
try {
$value = decrypt($payload);
} catch (\Illuminate\Contracts\Encryption\DecryptException $e) {
if (str_contains($e->getMessage(), 'MAC is invalid')) {
// key rotation gap — register APP_PREVIOUS_KEYS, or treat as expired session
}
throw $e;
} Prevention
- When rotating APP_KEY, always register the old key in previous_keys for grace-period decryption.
- Keep APP_KEY identical across all nodes sharing encrypted data.
- Never change the cipher without re-encrypting existing data.
When it happens
Trigger: Calling decrypt($payload) where the payload was encrypted with a key no longer configured — e.g. APP_KEY was rotated without registering the old key via previousKeys(), or the payload was tampered with. For CBC ciphers only; AEAD (GCM) ciphers skip MAC validation (the tag covers it).
Common situations: APP_KEY rotated; .env overwritten on deploy without preserving old key; copying encrypted data (cookies, queued jobs, cached values) between environments with different keys; session/cookie failures after key rotation; tampered query-string payloads.
Related errors
- Unsupported cipher or incorrect key length. Supported cipher
- Could not decrypt the data.
- The payload is invalid.
- Unable to use tag because the cipher algorithm does not supp
- The provided class must extend [{Collection::class}].
AI-assisted analysis of laravel/framework@bd6b5437e6 (2026-08-06).
Data as JSON: /data/errors/77cb7ea12447b492.json.
Report an issue: GitHub.