{"record":{"id":"474181bd7aa53fee","repo":"w7corp/easywechat","slug":"encrypt-aes-ecb-failed","errorCode":null,"errorMessage":"Encrypt AES ECB failed.","messagePattern":"Encrypt AES ECB failed\\.","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"src/Kernel/Support/AesEcb.php","lineNumber":24,"sourceCode":"\nuse EasyWeChat\\Kernel\\Contracts\\Aes;\nuse EasyWeChat\\Kernel\\Exceptions\\InvalidArgumentException;\n\nuse function base64_decode;\nuse function openssl_decrypt;\nuse function openssl_error_string;\n\nclass AesEcb implements Aes\n{\n    /**\n     * @throws InvalidArgumentException\n     */\n    public static function encrypt(string $plaintext, string $key, ?string $iv = null): string\n    {\n        $ciphertext = \\openssl_encrypt($plaintext, 'aes-256-ecb', $key, OPENSSL_RAW_DATA, (string) $iv);\n\n        if ($ciphertext === false) {\n            throw new InvalidArgumentException(openssl_error_string() ?: 'Encrypt AES ECB failed.');\n        }\n\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","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/Kernel/Support/AesEcb.php#L6-L42","documentation":"Support\\AesEcb::encrypt() uses the fixed 'aes-256-ecb' cipher with OPENSSL_RAW_DATA; a false return (surfaced via openssl_error_string(), fallback 'Encrypt AES ECB failed.') throws InvalidArgumentException. AES-256 needs exactly a 32-byte key; in the Pay v2 flow the sibling decrypt uses md5($apiKeyV2) as the key precisely because md5 yields 32 bytes.","triggerScenarios":"Calling AesEcb::encrypt() with a key that is not 32 bytes — e.g. the raw WeChat Pay v2 API key (arbitrary length) instead of md5($key), or an undecoded base64 key.","commonSituations":"Copy-pasting decrypt code into an encrypt path without the md5() transform, key-length assumptions carried over from AES-128 helpers, silent key truncation in config.","solutions":["Pass a 32-byte key: for WeChat Pay v2 semantics use md5($apiKey), matching Pay\\Server's usage","Log openssl_error_string() on failure — it pinpoints the key-length problem","Add a strlen($key) === 32 assertion before calling so mis-sized keys fail loudly at the call site"],"exampleFix":"// before\n$cipher = AesEcb::encrypt($xml, $apiV2Key); // arbitrary length -> fails\n\n// after\n$cipher = AesEcb::encrypt($xml, md5($apiV2Key)); // 32-byte key, matches Pay\\Server decrypt","handlingStrategy":"validation","validationCode":"// Assert the 32-byte key requirement before encrypting (aes-256-ecb)\nif (strlen($key) !== 32) {\n    throw new InvalidArgumentException('AesEcb key must be 32 bytes (use md5($apiV2Key) for WeChat Pay v2), got '.strlen($key));\n}\n$ciphertext = AesEcb::encrypt($plaintext, $key);","typeGuard":"function isRawAes256Key(string $key): bool\n{\n    return strlen($key) === 32;\n}","tryCatchPattern":"try {\n    $ciphertext = AesEcb::encrypt($xml, $key);\n} catch (\\EasyWeChat\\Kernel\\Exceptions\\InvalidArgumentException $e) {\n    if (strlen($key) !== 32) {\n        $ciphertext = AesEcb::encrypt($xml, md5($key)); // v2 pay semantics\n    } else {\n        throw $e;\n    }\n}","preventionTips":["Derive the v2 Pay key once (md5) at config load and reuse the 32-byte value everywhere","Assert key length at the call site so mis-sized keys fail with a clear message"],"tags":["php","easywechat","encryption","aes","wechat-pay","openssl"],"backgroundTag":"invalid-aes-key","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}