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

  1. 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.
  2. If rotation was intentional and old data is disposable, clear the affected cookies/sessions/queues so clients get fresh payloads.
  3. Verify APP_KEY is identical across all servers/containers sharing the encrypted data.
  4. 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 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


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