firefly-iii/firefly-iii · critical · FireflyException

The MAC is invalid.

Error message

The MAC is invalid.

What it means

Thrown by upgrade:480-decrypt-all when Laravel's Crypt::decrypt() fails with 'The MAC is invalid.' That message means the ciphertext's message authentication code does not match the current APP_KEY — i.e. the field was encrypted with a different key than the one in .env. The command re-throws it as FireflyException because silently passing wrong-key data through would corrupt the upgrade.

Source

Thrown at app/Console/Commands/Upgrade/RemovesDatabaseDecryption.php:180

        return (bool) $configVar?->data;
    }

    /**
     * Tries to decrypt data. Will only throw an exception when the MAC is invalid.
     *
     * @param mixed $value
     *
     * @return string
     *
     * @throws FireflyException
     */
    private function tryDecrypt($value)
    {
        try {
            $value = Crypt::decrypt($value);
        } catch (DecryptException $e) {
            if ('The MAC is invalid.' === $e->getMessage()) {
                throw new FireflyException($e->getMessage(), 0, $e);
            }
        }

        return $value;
    }
}

View on GitHub (pinned to fd8791d08d)

Solutions

  1. Restore the ORIGINAL APP_KEY that encrypted the data in .env, clear config cache (php artisan config:clear), then re-run upgrade:480-decrypt-all.
  2. Search old .env files, container logs, or backups for the previous APP_KEY — it is the only way to decrypt the data.
  3. Once decryption succeeds and the upgrade completes, you may rotate APP_KEY again (future data uses the new key).
  4. If the old key is lost, the encrypted fields are unrecoverable; accept the loss (usually only 2FA secrets and similar fields) and re-set them in the UI.

Example fix

# before (.env after reinstall)
APP_KEY=base64:NEWKEY...
# upgrade:480-decrypt-all => The MAC is invalid.

# after (restore the key that encrypted the DB)
APP_KEY=base64:ORIGINALKEY...
php artisan config:clear
php artisan upgrade:480-decrypt-all
Defensive patterns

Strategy: try-catch

Validate before calling

// verify APP_KEY can decrypt a known encrypted value BEFORE mass upgrade
use Illuminate\Support\Facades\Crypt;
try {
    $probe = \FireflyIII\Models\Preference::where('name', 'two_factor_secret')->first();
    if (null !== $probe && null !== $probe->data_encrypted) {
        Crypt::decryptString($probe->data_encrypted);
    }
} catch (\Illuminate\Contracts\Encryption\DecryptException $e) {
    // APP_KEY mismatch: restore the original key before running upgrade:480-decrypt-all
}

Try / catch

use FireflyIII\Exceptions\FireflyException;

try {
    $this->artisan('upgrade:480-decrypt-all');
} catch (FireflyException $e) {
    if ('The MAC is invalid.' === $e->getMessage()) {
        // stop: current APP_KEY != key that encrypted the data. Restore old APP_KEY, config:clear, re-run.
        // do NOT continue the upgrade with mismatched data
    }
}

Prevention

When it happens

Trigger: Running php artisan firefly-iii:upgrade-database (or upgrade:480-decrypt-all) after changing APP_KEY or after restoring a database dump whose data was encrypted under an old key. Every encrypted column (e.g. two_factor_secret, older encrypted metadata) then fails MAC verification.

Common situations: Reinstalling Firefly III and generating a fresh APP_KEY while reusing the old database. Docker deployments without a pinned APP_KEY regenerating keys on rebuild. Copying .env.example into production (new key) with a restored backup. Rotating APP_KEY for security without planning re-encryption.

Related errors


AI-assisted analysis of firefly-iii/firefly-iii@fd8791d08d (2026-08-17). Data as JSON: /api/errors/667e98e0eee2113e. Report an issue: GitHub.