laravel/framework · critical · DecryptException
The MAC is invalid.
Error message
The MAC is invalid.
What it means
During decryption, Encrypter iterates over the current and any previous keys, recomputing the MAC for each; if shouldValidateMac() is true and no key produced a matching MAC ($validKey === null), it throws DecryptException('The MAC is invalid.'). This is the canonical 'wrong key' or 'tampered payload' failure — the payload parsed fine but its MAC does not validate against any available key.
Solutions
- Register the old key as a previous key so decryption can fall back: set app.previous_keys in config/app.php (Laravel) so both keys are tried.
- If the key genuinely rotated and old data is unrecoverable, treat the payload as expired and re-issue (e.g. flush sessions, re-set encrypted cookies).
- Verify the payload was not truncated or altered in transport (cookie size limits, proxy rewrites).
Example fix
// before
// config/app.php: 'key' => env('APP_KEY'),
// after (keep the old key available for decryption)
// .env: APP_KEY=newkey
// APP_KEY_OLD=oldkey
// config/app.php:
'key' => env('APP_KEY'),
'previous_keys' => [
env('APP_KEY_OLD'),
], Defensive patterns
Strategy: try-catch
Validate before calling
// register previous keys so MAC validation can fall back
// config/app.php
'key' => env('APP_KEY'),
'previous_keys' => array_filter([
env('APP_KEY_OLD'),
]), Try / catch
use Illuminate\Contracts\Encryption\DecryptException;
try {
return Crypt::decrypt($payload);
} catch (DecryptException $e) {
if (str_contains($e->getMessage(), 'MAC is invalid')) {
// key rotation scenario — invalidate the session/data and re-issue
return null;
}
throw $e;
} Prevention
- On APP_KEY rotation, keep the old key in app.previous_keys until all old ciphertexts are re-encrypted.
- Never share encrypted cookies/sessions across environments with different APP_KEY values.
- Treat a sudden spike of MAC-invalid errors as a key mismatch signal.
When it happens
Trigger: Calling Crypt::decrypt() on a ciphertext that was encrypted with a key no longer in APP_KEY/previous_keys; on a payload whose value/iv/tag was altered (tampering); after an APP_KEY rotation without listing the old key in previous_keys.
Common situations: Rotating APP_KEY and forgetting to register the old key via Encrypter::previousKeys() / the app.previous_keys config; copying encrypted cookies/sessions between environments with different keys; a user manually editing an encrypted cookie; cross-environment data migration.
Related errors
- Could not decrypt the data.
- The payload is invalid.
- Unsupported cipher or incorrect key length. Supported…
- Could not encrypt the data.
- Add [ ] to fillable property to allow mass assignment on […
AI-assisted analysis of laravel/framework@e0f6eb3518 (2026-08-11).
Data as JSON: /api/errors/77cb7ea12447b492.
Report an issue: GitHub.
Appendix: 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 e0f6eb3518)