{"record":{"id":"15f7952996c8cfa5","repo":"yiisoft/yii2","slug":"openssl-failure-on-decryption-error","errorCode":null,"errorMessage":"OpenSSL failure on decryption: {error}","messagePattern":"OpenSSL failure on decryption: (.+?)","errorType":"exception","errorClass":"yii\\base\\Exception","httpStatus":null,"severity":"error","filePath":"framework/base/Security.php","lineNumber":278,"sourceCode":"        $keySalt = StringHelper::byteSubstr($data, 0, $keySize);\n        if ($passwordBased) {\n            $key = $this->pbkdf2($this->kdfHash, $secret, $keySalt, $this->derivationIterations, $keySize);\n        } else {\n            $key = $this->hkdf($this->kdfHash, $secret, $keySalt, $info, $keySize);\n        }\n\n        $authKey = $this->hkdf($this->kdfHash, $key, null, $this->authKeyInfo, $keySize);\n        $data = $this->validateData(StringHelper::byteSubstr($data, $keySize, null), $authKey);\n        if ($data === false) {\n            return false;\n        }\n\n        $iv = StringHelper::byteSubstr($data, 0, $blockSize);\n        $encrypted = StringHelper::byteSubstr($data, $blockSize, null);\n\n        $decrypted = openssl_decrypt($encrypted, $this->cipher, $key, OPENSSL_RAW_DATA, $iv);\n        if ($decrypted === false) {\n            throw new \\yii\\base\\Exception('OpenSSL failure on decryption: ' . openssl_error_string());\n        }\n\n        return $decrypted;\n    }\n\n    /**\n     * Derives a key from the given input key using the standard HKDF algorithm.\n     * Implements HKDF specified in [RFC 5869](https://tools.ietf.org/html/rfc5869).\n     * Recommend use one of the SHA-2 hash algorithms: sha224, sha256, sha384 or sha512.\n     * @param string $algo a hash algorithm supported by `hash_hmac()`, e.g. 'SHA-256'\n     * @param string $inputKey the source key\n     * @param string|null $salt the random salt\n     * @param string|null $info optional info to bind the derived key material to application-\n     * and context-specific information, e.g. a user ID or API version, see\n     * [RFC 5869](https://tools.ietf.org/html/rfc5869)\n     * @param int $length length of the output key in bytes. If 0, the output key is\n     * the length of the hash algorithm output.\n     * @throws InvalidArgumentException when HMAC generation fails.","sourceCodeStart":260,"sourceCodeEnd":296,"githubUrl":"https://github.com/yiisoft/yii2/blob/66f00d18a29b520f85e8e8f1e32d1e7e7b556cac/framework/base/Security.php#L260-L296","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","On OpenSSL 3, enable the legacy provider for old methods, or run a one-time re-encryption migration to a modern cipher.","Store ciphertext in binary-safe BLOB columns with sufficient length, never TEXT/VARCHAR.","Catch the Exception and treat the record as undecryptable: fail closed, alert, and do not retry blindly."],"exampleFix":"// before\n$plain = Yii::$app->security->decryptByKey($row['secret'], $key); // cipher changed since encryption\n\n// after\n$legacy = new \\yii\\base\\Security(['cipher' => 'AES-256-CBC']); // settings used when data was written\n$plain = $legacy->decryptByKey($row['secret'], $key);\nif ($plain === false) {\n    // MAC mismatch — wrong key or corrupted payload\n}","handlingStrategy":"try-catch","validationCode":"// Guard the ciphertext before decrypting: sane length (keySalt + MAC + IV + data)\n$minLength = Yii::$app->security->derivationKeySize ?? 32; // at least keySalt + MAC bytes\nif ($stored === null || strlen($stored) < 64) {\n    return null; // not decryptable — treat as absent\n}\ntry {\n    return Yii::$app->security->decryptByKey($stored, $key);\n} catch (\\yii\\base\\Exception $e) {\n    \\Yii::error('Stored ciphertext undecryptable: ' . $e->getMessage(), 'security');\n    return null;\n}","typeGuard":null,"tryCatchPattern":"try {\n    $plain = Yii::$app->security->decryptByKey($data, $key);\n} catch (\\yii\\base\\Exception $e) {\n    // MAC passed but OpenSSL failed — cipher/config drift or OpenSSL 3 legacy refusal\n    \\Yii::error('Decrypt failed: ' . $e->getMessage(), 'security');\n    $plain = false;\n}\nif ($plain === false) {\n    // wrong key OR undecryptable — handle as invalid data, fail closed\n}","preventionTips":["Version the encryption settings: store a header byte/key-derivation tag with each ciphertext so legacy blobs can be decrypted with matching settings.","Never change cipher/KDF config without a planned re-encryption migration.","Use binary-safe BLOB columns for ciphertext; avoid charset conversions on the column.","On OpenSSL 3 hosts, decide up front whether legacy ciphers are needed and configure providers explicitly."],"tags":["security","decryption","openssl","config-drift","yii2"],"backgroundTag":"openssl-decryption-failed","analyzedSha":"66f00d18a29b520f85e8e8f1e32d1e7e7b556cac","analyzedAt":"2026-08-17T05:17:23.470Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}