yiisoft/yii2 · error · yii\base\Exception

OpenSSL failure on decryption: {error}

Error message

OpenSSL failure on decryption: {error}

What it means

Security::decryptByKey()/decryptByPassword() first verify the MAC via validateData() — a wrong key or password returns false without throwing — and only then call openssl_decrypt(); a false return is converted into yii\base\Exception with OpenSSL's error text. Reaching the throw means the MAC passed but OpenSSL still could not decrypt: typically the cipher/block-size settings differ from those used at encryption time, or the runtime OpenSSL (e.g. OpenSSL 3 legacy provider) refuses the stored data's method.

Source

Thrown at framework/base/Security.php:278

        $keySalt = StringHelper::byteSubstr($data, 0, $keySize);
        if ($passwordBased) {
            $key = $this->pbkdf2($this->kdfHash, $secret, $keySalt, $this->derivationIterations, $keySize);
        } else {
            $key = $this->hkdf($this->kdfHash, $secret, $keySalt, $info, $keySize);
        }

        $authKey = $this->hkdf($this->kdfHash, $key, null, $this->authKeyInfo, $keySize);
        $data = $this->validateData(StringHelper::byteSubstr($data, $keySize, null), $authKey);
        if ($data === false) {
            return false;
        }

        $iv = StringHelper::byteSubstr($data, 0, $blockSize);
        $encrypted = StringHelper::byteSubstr($data, $blockSize, null);

        $decrypted = openssl_decrypt($encrypted, $this->cipher, $key, OPENSSL_RAW_DATA, $iv);
        if ($decrypted === false) {
            throw new \yii\base\Exception('OpenSSL failure on decryption: ' . openssl_error_string());
        }

        return $decrypted;
    }

    /**
     * Derives a key from the given input key using the standard HKDF algorithm.
     * Implements HKDF specified in [RFC 5869](https://tools.ietf.org/html/rfc5869).
     * Recommend use one of the SHA-2 hash algorithms: sha224, sha256, sha384 or sha512.
     * @param string $algo a hash algorithm supported by `hash_hmac()`, e.g. 'SHA-256'
     * @param string $inputKey the source key
     * @param string|null $salt the random salt
     * @param string|null $info optional info to bind the derived key material to application-
     * and context-specific information, e.g. a user ID or API version, see
     * [RFC 5869](https://tools.ietf.org/html/rfc5869)
     * @param int $length length of the output key in bytes. If 0, the output key is
     * the length of the hash algorithm output.
     * @throws InvalidArgumentException when HMAC generation fails.

View on GitHub (pinned to 66f00d18a2)

Solutions

  1. Restore the exact cipher/kdf settings in force when the data was encrypted — check git history of the security config and instantiate a dedicated Security instance with those values for legacy data.
  2. On OpenSSL 3, enable the legacy provider for old methods, or run a one-time re-encryption migration to a modern cipher.
  3. Store ciphertext in binary-safe BLOB columns with sufficient length, never TEXT/VARCHAR.
  4. Catch the Exception and treat the record as undecryptable: fail closed, alert, and do not retry blindly.

Example fix

// before
$plain = Yii::$app->security->decryptByKey($row['secret'], $key); // cipher changed since encryption

// after
$legacy = new \yii\base\Security(['cipher' => 'AES-256-CBC']); // settings used when data was written
$plain = $legacy->decryptByKey($row['secret'], $key);
if ($plain === false) {
    // MAC mismatch — wrong key or corrupted payload
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Guard the ciphertext before decrypting: sane length (keySalt + MAC + IV + data)
$minLength = Yii::$app->security->derivationKeySize ?? 32; // at least keySalt + MAC bytes
if ($stored === null || strlen($stored) < 64) {
    return null; // not decryptable — treat as absent
}
try {
    return Yii::$app->security->decryptByKey($stored, $key);
} catch (\yii\base\Exception $e) {
    \Yii::error('Stored ciphertext undecryptable: ' . $e->getMessage(), 'security');
    return null;
}

Try / catch

try {
    $plain = Yii::$app->security->decryptByKey($data, $key);
} catch (\yii\base\Exception $e) {
    // MAC passed but OpenSSL failed — cipher/config drift or OpenSSL 3 legacy refusal
    \Yii::error('Decrypt failed: ' . $e->getMessage(), 'security');
    $plain = false;
}
if ($plain === false) {
    // wrong key OR undecryptable — handle as invalid data, fail closed
}

Prevention

When it happens

Trigger: Changing Yii::$app->security->cipher (e.g. AES-256-CBC to AES-128-CBC) after data was encrypted — MAC still passes because the auth key does not depend on the cipher, but IV/ciphertext slicing breaks; decrypting legacy data on OpenSSL 3.x where the original method moved to the legacy provider; runtime config drift between machines sharing one database of encrypted blobs.

Common situations: Upgrading PHP 7.4 to 8.x with OpenSSL 1.1 to 3.x in between; an ops 'cleanup' of security component defaults without re-encryption; multi-region setups where one node has different OpenSSL; decrypting data in a new integration written years after encryption.

Related errors


AI-assisted analysis of yiisoft/yii2@66f00d18a2 (2026-08-17). Data as JSON: /api/errors/15f7952996c8cfa5. Report an issue: GitHub.