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 stringView on GitHub (pinned to b7419de9cd)
Solutions
- 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.
- Confirm cipher and signing settings match the ones used at encryption time (setCipher, useSigning).
- If keys were rotated, keep the old key and try it for legacy records, then re-encrypt with the new key.
- 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
- Fingerprint the key (hash of key) and monitor it across environments so drift is caught before data loss.
- Store the key id alongside encrypted payloads when rotation is planned, so the right key can be selected at decrypt time.
- Keep signing enabled (default) - it converts silent garbage into a clean, catchable exception.
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
- Decryption key cannot be empty
- Encryption key cannot be empty
- The provided input is too short for the selected cipher.
- Cannot calculate Random Pseudo Bytes
- The auth tag length must be between 4 and 16 bytes.
AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21).
Data as JSON: /api/errors/2d5146c0484ffe02.
Report an issue: GitHub.