phalcon/cphalcon · error · EmptyEncryptionKey

Encryption key cannot be empty

Error message

Encryption key cannot be empty

What it means

Crypt::encrypt() needs a key: the second argument if given, otherwise the one set via setKey(). If both are empty it throws EmptyEncryptionKey before generating an IV. Like its decrypt twin, this is a hard configuration failure - Phalcon refuses to encrypt with no key rather than silently using empty material.

Source

Thrown at phalcon/Encryption/Crypt.zep:337

     *
     * @param string      $input
     * @param string|null $key
     *
     * @return string
     * @throws Exception
     */
    public function encrypt(string input, string key = null) -> string
    {
        var blockSize, cipher, digest, encryptKey, encrypted, iv, ivLength,
            mode, padded;

        let encryptKey = this->key;
        if true !== empty(key) {
            let encryptKey = key;
        }

        if true === empty(encryptKey) {
            throw new EmptyEncryptionKey();
        }

        let cipher   = this->cipher,
            ivLength = this->ivLength;

        this->checkCipherHashIsAvailable(cipher, "cipher");

        let mode      = this->getMode(),
            blockSize = this->getBlockSize(mode);

        try {
            let iv = this->phpOpensslRandomPseudoBytes(ivLength);
        } catch \Throwable {
            throw new RandomBytesGenerationFailed();
        }

        let padded = this->encryptGetPadded(mode, input, blockSize);

View on GitHub (pinned to b7419de9cd)

Solutions

  1. Set the key at construction time in your DI setup: $crypt->setKey($config->encryption->key) after asserting it is non-empty.
  2. Or pass it per call: $crypt->encrypt($data, $key).
  3. Add a boot-time assertion: if (empty($_ENV['APP_ENCRYPTION_KEY'])) { throw new RuntimeException('Encryption key missing'); } so the app fails fast.
  4. Generate the key with bin2hex(random_bytes(32)) once and store it in a secret manager/env - never derive it from a password.

Example fix

// before
$di->set('crypt', function () {
    $crypt = new \Phalcon\Encryption\Crypt();
    return $crypt; // key never configured
});
$stored = $di->get('crypt')->encrypt($secret); // throws

// after
$di->set('crypt', function () use ($config) {
    $key = $config->path('encryption.key');
    if (empty($key)) {
        throw new \RuntimeException('encryption.key is not configured');
    }
    return (new \Phalcon\Encryption\Crypt())->setKey($key);
});
Defensive patterns

Strategy: validation

Validate before calling

$key = $_ENV['APP_ENCRYPTION_KEY'] ?? '';
if ('' === $key) {
    throw new \RuntimeException('APP_ENCRYPTION_KEY is not set');
}
$crypt->setKey($key);
$cipherText = $crypt->encrypt($data);

Type guard

function hasCryptKey(\Phalcon\Encryption\Crypt $crypt): bool
{
    return '' !== $crypt->getKey();
}

Try / catch

try {
    $stored = $crypt->encrypt($data);
} catch (\Phalcon\Encryption\Crypt\Exception\EmptyEncryptionKey $e) {
    throw new \RuntimeException('Cannot encrypt: no key configured', 0, $e);
}

Prevention

When it happens

Trigger: new Crypt() followed directly by encrypt($data); setKey('') then encrypt($data) without the second argument; a DI-shared Crypt whose key is injected from an empty config/env value in this environment.

Common situations: Missing APP_KEY-style env var in CI, Docker, or a new developer's .env; config cached before the key existed; key value loaded with a wrong config path so it resolves to null and casts to empty string.

Related errors


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