phalcon/cphalcon · error · DecryptionFailed
Could not decrypt data
Error message
Could not decrypt data
What it means
Crypt's internal decryptGcmCcm/decryptCbc path calls openssl_decrypt(); when it returns false (OpenSSL could not decrypt), Phalcon throws DecryptionFailed('Could not decrypt data'). Unlike Mismatch (signing), this is OpenSSL itself rejecting the input: key length not matching the cipher, corrupted ciphertext/IV, or mode/cipher mismatch.
Source
Thrown at phalcon/Encryption/Crypt.zep:838
cipher,
decryptKey,
OPENSSL_RAW_DATA,
iv,
authTag,
authData
);
} else {
let decrypted = openssl_decrypt(
cipherText,
cipher,
decryptKey,
OPENSSL_RAW_DATA,
iv
);
}
if (false === decrypted) {
throw new DecryptionFailed();
}
return decrypted;
}
/**
* @param string $mode
* @param string $input
* @param int $blockSize
*
* @return string
* @throws Exception
*/
protected function encryptGetPadded(
string mode,
string input,
int blockSize
) -> string {View on GitHub (pinned to b7419de9cd)
Solutions
- Use keys of the exact byte length the cipher requires (32 bytes for aes-256-*); derive from passwords with hash('sha256', $password, true) or PBKDF2/argon2 rather than passing the password raw.
- Ensure the payload reaches decrypt() byte-identical: store base64 (encryptBase64/decryptBase64) or BLOB-safe columns, never URL params or trimmed strings.
- Verify cipher, mode, and auth-tag handling match the encrypt side (for GCM, the tag is appended to the ciphertext by Phalcon - keep storage big enough).
- Catch DecryptionFailed and Mismatch together at the call site and treat both as 'unreadable payload'.
Example fix
// before
$crypt->setKey('my-password'); // 11 bytes, wrong for aes-256
$plain = $crypt->decrypt($blob); // openssl_decrypt -> false -> throws
// after
$crypt->setKey(hash('sha256', 'my-password', true)); // exact 32 bytes
$plain = $crypt->decrypt($blob); Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight the most common cause: exact key length for the cipher
$keyLengths = ['aes-128' => 16, 'aes-192' => 24, 'aes-256' => 32];
$prefix = substr($crypt->getCipher(), 0, 7); // e.g. 'aes-256'
if (isset($keyLengths[$prefix]) && strlen($crypt->getKey()) !== $keyLengths[$prefix]) {
throw new \RuntimeException("Key must be {$keyLengths[$prefix]} bytes for {$crypt->getCipher()}");
} Try / catch
use Phalcon\Encryption\Crypt\Exception\DecryptionFailed;
use Phalcon\Encryption\Crypt\Exception\Mismatch;
try {
$plain = $crypt->decrypt($payload);
} catch (DecryptionFailed | Mismatch $e) {
// unreadable payload: wrong key/length or corrupted bytes - reject, never echo raw
$logger->warning('Undecryptable payload: ' . $e->getMessage());
throw new AppException\UnrecoverablePayload('Stored data cannot be decrypted', 0, $e);
} Prevention
- Derive keys to the cipher's exact byte length (32 bytes for aes-256) and assert the length at boot.
- Round-trip test encrypt/decrypt in CI for every environment/key pair.
- Persist ciphertext as base64/BLOB to prevent truncation of IV or GCM tag bytes.
When it happens
Trigger: Decrypting with a key of the wrong length for the cipher (e.g., a 16-byte passphrase with aes-256); ciphertext or IV bytes corrupted/truncated (but still >= ivLength so the length check passed); data encrypted with a different cipher or by another library; GCM tag bytes lost because storage stripped trailing bytes.
Common situations: Using a human password directly as the key instead of a derived 32-byte key; DB columns/base64 layers dropping trailing NUL bytes from the appended auth tag; switching ciphers after data was stored; decrypting data from an external system with a different padding/signing scheme.
Related errors
- Could not encrypt data
- The provided input is too short for the selected cipher.
- Cannot calculate Random Pseudo Bytes
- The {} algorithm '{}' is not supported on this system.
- This class requires the openssl extension for PHP
AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21).
Data as JSON: /api/errors/6d552f36bd941a85.
Report an issue: GitHub.