phalcon/cphalcon · error · Mismatch

Hash does not match.

Error message

Hash does not match.

What it means

When signing is enabled (useSigning, on by default in Phalcon 5), encrypt() appends an HMAC-SHA256 digest of the padded plaintext keyed with the encryption key, and decrypt() recomputes it over the decrypted-padded data and compares in constant time. A mismatch means the plaintext+key+cipher combination does not reproduce the stored digest, so the data was encrypted with a different key or was modified. Phalcon throws Mismatch('Hash does not match.') and refuses to return the plaintext.

Source

Thrown at phalcon/Encryption/Crypt.zep:276

        } else {
            let cipherText = mb_substr(input, ivLength, null, "8bit");
        }

        let decrypted = this->decryptGcmCcmAuth(
            mode,
            cipherText,
            decryptKey,
            iv
        );

        if true === this->useSigning {
            /**
             * Checks on the decrypted message digest using the HMAC method.
             * The check runs against the padded plaintext, before unpadding,
             * and uses hash_equals() so that the comparison is constant-time.
             */
            if true !== this->phpHashEquals(this->phpHashHmac(hashAlgorithm, decrypted, decryptKey, true), digest) {
                throw new Mismatch("Hash does not match.");
            }
        }

        return this->decryptGetUnpadded(
            mode,
            blockSize,
            decrypted
        );
    }

    /**
     * Decrypt a text that is coded as a base64 string.
     *
     * @param string     $input
     * @param mixed|null $key
     * @param bool       $safe
     *
     * @return string

View on GitHub (pinned to b7419de9cd)

Solutions

  1. Confirm the exact same key string (byte-for-byte, no trimming differences) is used on both sides - dump strlen/hash of the key in each environment.
  2. Confirm cipher and signing settings match the ones used at encryption time (setCipher, useSigning).
  3. If keys were rotated, keep the old key and try it for legacy records, then re-encrypt with the new key.
  4. If the digest was lost by truncation (e.g. varchar column too short), widen storage and re-encrypt; treat existing records as unrecoverable unless a backup key/data pair exists.

Example fix

// before
$crypt->setKey(getenv('NEW_APP_KEY'));
$plain = $crypt->decrypt($legacyRow['card_pan']); // encrypted with OLD_APP_KEY -> Mismatch

// after
$crypt->setKey(getenv('NEW_APP_KEY'));
try {
    $plain = $crypt->decrypt($legacyRow['card_pan']);
} catch (\Phalcon\Encryption\Crypt\Exception\Mismatch $e) {
    $legacy = new \Phalcon\Encryption\Crypt();
    $legacy->setKey(getenv('OLD_APP_KEY'));
    $plain = $legacy->decrypt($legacyRow['card_pan']);
    $row['card_pan'] = $crypt->encrypt($plain); // re-encrypt, persist
}
Defensive patterns

Strategy: try-catch

Validate before calling

// No pre-validation can prove an HMAC will match (that is the point of the MAC),
// but you can assert key consistency across servers:
if (hash('sha256', $crypt->getKey(), true) !== $expectedKeyFingerprint) {
    throw new \RuntimeException('Encryption key fingerprint differs from encrypting system');
}

Try / catch

try {
    $plain = $crypt->decrypt($cipherText);
} catch (\Phalcon\Encryption\Crypt\Exception\Mismatch $e) {
    // integrity failure: wrong key or tampered payload - treat as unreadable, never fall back to raw ciphertext
    $logger->warning('Crypt Mismatch for record #{id}', ['id' => $id]);
    throw new AppException\UnrecoverablePayload('Stored payload cannot be verified', 0, $e);
}

Prevention

When it happens

Trigger: decrypt() with a key different from the one used to encrypt (key rotation, wrong env); ciphertext bytes truncated or altered; cipher/mode changed between encrypt and decrypt (different padding changes the HMAC input); signing disabled on encrypt and enabled on decrypt (digest empty string vs computed); or data encrypted by a different library/version with another padding scheme.

Common situations: Key rotation without re-encrypting stored data; multi-server deployments where one node has a stale key; copying encrypted columns between environments; upgrading across Phalcon versions that changed padding defaults. Note: a wrong key can also surface as DecryptionFailed instead - both mean key/data mismatch.

Related errors


AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21). Data as JSON: /api/errors/2d5146c0484ffe02. Report an issue: GitHub.