{"record":{"id":"06769bfe057bb73c","repo":"w7corp/easywechat","slug":"decrypt-aes-ecb-failed","errorCode":null,"errorMessage":"Decrypt AES ECB failed.","messagePattern":"Decrypt AES ECB failed\\.","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"src/Kernel/Support/AesEcb.php","lineNumber":44,"sourceCode":"\n        return \\base64_encode($ciphertext);\n    }\n\n    /**\n     * @throws InvalidArgumentException\n     */\n    public static function decrypt(string $ciphertext, string $key, ?string $iv = null): string\n    {\n        $plaintext = openssl_decrypt(\n            base64_decode($ciphertext, true) ?: '',\n            'aes-256-ecb',\n            $key,\n            OPENSSL_RAW_DATA,\n            (string) $iv\n        );\n\n        if ($plaintext === false) {\n            throw new InvalidArgumentException(openssl_error_string() ?: 'Decrypt AES ECB failed.');\n        }\n\n        return $plaintext;\n    }\n}\n","sourceCodeStart":26,"sourceCodeEnd":50,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/Kernel/Support/AesEcb.php#L26-L50","documentation":"Thrown when openssl_decrypt() returns false for the aes-256-ecb decryption of a base64 payload. The only internal caller is Pay\\Server::decodeXmlMessage (src/Pay/Server.php:229), which decrypts the legacy WeChat Pay v2 XML callback field req_info using md5($v2SecretKey) as the key. Because OPENSSL_RAW_DATA still verifies PKCS7 padding, a wrong V2 key (garbage plaintext fails the padding check), a key that is not exactly 32 bytes, or ciphertext that fails strict base64_decode all make OpenSSL return false and raise this exception.","triggerScenarios":"Handling a WeChat Pay v2 XML callback (refund notifications etc.) with a wrong or rotated V2 secret key; the req_info CDATA value truncated or re-encoded by earlier XML handling so base64_decode($c, true) fails; calling AesEcb::decrypt() directly with strlen($key) !== 32 (e.g. the raw 32-char key re-hashed, or a raw 16-byte md5 digest instead of the 32-char hex string).","commonSituations":"V2 key rotated in the merchant console but the app config still has the old one; custom code passing the raw key or md5($key, true) (16 bytes) instead of md5($key) (32 hex chars); test environments pointing at production keys; XML middleware stripping CDATA sections.","solutions":["Verify the V2 key is the exact 32-char API v2 key from the WeChat merchant console and that the decrypt key is md5($v2Key) (32 hex chars, valid for aes-256).","Log base64_decode($reqInfo, true) before decrypting; if it returns false the XML layer upstream mangled req_info — fix parsing instead of keys.","Assert strlen($key) === 32 before calling; aes-256-* ciphers only accept 32-byte keys.","If keys are correct and it still fails, reproduce with the openssl CLI to see the raw OpenSSL error (usually a padding error proving the key is wrong)."],"exampleFix":"// before: raw v2 key used directly (wrong length for aes-256)\n$cipher = AesEcb::decrypt($reqInfo, $v2Key, iv: '');\n// after: the v2 protocol derives a 32-byte hex key via md5()\n$cipher = AesEcb::decrypt($reqInfo, md5($v2Key), iv: '');","handlingStrategy":"validation","validationCode":"$key = md5($v2Key);\nif (strlen($key) !== 32) { throw new RuntimeException('aes-256-ecb key must be 32 bytes'); }\nif ($reqInfo === '' || base64_decode($reqInfo, true) === false) { throw new RuntimeException('req_info is not valid base64'); }\n$plain = AesEcb::decrypt($reqInfo, $key, iv: '');","typeGuard":null,"tryCatchPattern":"try { $plain = AesEcb::decrypt($reqInfo, md5($v2Key), iv: ''); } catch (\\EasyWeChat\\Kernel\\Exceptions\\InvalidArgumentException $e) { \\Log::warning('v2 callback decrypt failed: '.$e->getMessage()); return new \\Nyholm\\Psr7\\Response(200, [], 'fail'); }","preventionTips":["Keep the V2 key in one env-driven config value and derive the md5 key in a single place","Assert strlen(md5($v2Key)) === 32 at boot","Hand the SDK the raw callback body instead of pre-parsing the XML yourself"],"tags":["php","openssl","aes-ecb","wechat-pay-v2","decryption"],"backgroundTag":"aes-ecb-decryption-failed","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}