{"record":{"id":"9bcb2b901d2c00b9","repo":"w7corp/easywechat","slug":"decrypt-failed","errorCode":null,"errorMessage":"Decrypt failed","messagePattern":"Decrypt failed","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"src/Kernel/Support/AesGcm.php","lineNumber":57,"sourceCode":"\n        return base64_encode($ciphertext.$tag);\n    }\n\n    /**\n     * @throws InvalidArgumentException\n     */\n    public static function decrypt(string $ciphertext, string $key, ?string $iv = null, string $aad = ''): string\n    {\n        $ciphertext = base64_decode($ciphertext);\n\n        $tag = substr($ciphertext, -self::BLOCK_SIZE);\n\n        $ciphertext = substr($ciphertext, 0, -self::BLOCK_SIZE);\n\n        $plaintext = openssl_decrypt($ciphertext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, (string) $iv, $tag, $aad);\n\n        if ($plaintext === false) {\n            throw new InvalidArgumentException(openssl_error_string() ?: 'Decrypt failed');\n        }\n\n        return $plaintext;\n    }\n}\n","sourceCodeStart":39,"sourceCodeEnd":63,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/Kernel/Support/AesGcm.php#L39-L63","documentation":"Thrown when openssl_decrypt() returns false for aes-256-gcm after the class splits the last 16 bytes (AesGcm::BLOCK_SIZE) off the base64-decoded payload as the auth tag. Internally Pay\\Server::decodeJsonMessage/decodeXmlMessage (src/Pay/Server.php:238,278) call it with the merchant secretKey (APIv3 key), the notification nonce and associated_data. GCM authentication fails if any byte is wrong: bad key length or value, wrong nonce, wrong AAD, or a ciphertext shorter than 16 bytes.","triggerScenarios":"Processing a WeChat Pay v3 notification with a secret_key that is not the current 32-char APIv3 key; swapping nonce or associated_data between different notifications; resource.ciphertext shorter than 16 decoded bytes (tag substr() eats the whole string); config value with a trailing newline making the key 33 chars.","commonSituations":"APIv3 key rotated in the merchant console but the app config still holds the old one; staging and production keys swapped; notification JSON re-parsed/logged and associated_data lost; notifications delivered after a key change encrypted with the previous key.","solutions":["Make config secret_key equal the current APIv3 key — exactly 32 chars, trim() it.","Pass nonce and associated_data exactly as received in resource (e.g. 'transaction' or 'refund').","Validate before calling: base64-decoded ciphertext must be longer than 16 bytes and strlen($key) === 32.","If the key was rotated, keep the old key available until pending notifications are drained, or re-download platform certs and reprocess."],"exampleFix":"// before: stale/short APIv3 key straight from config\n$merchant = new Merchant(..., secretKey: $config['secret_key']);\n// after: validate the 32-char key before building the pay app\n$secretKey = trim((string) $config['secret_key']);\nif (strlen($secretKey) !== 32) { throw new RuntimeException('APIv3 key must be 32 chars'); }\n$app = new Pay\\Application([...defaults, 'secret_key' => $secretKey]);","handlingStrategy":"validation","validationCode":"$raw = base64_decode($ciphertext, true);\nif ($raw === false || strlen($raw) <= 16) { throw new RuntimeException('ciphertext must be base64 and longer than the 16-byte tag'); }\nif (strlen($secretKey) !== 32) { throw new RuntimeException('APIv3 key must be exactly 32 chars'); }","typeGuard":null,"tryCatchPattern":"try { $plain = AesGcm::decrypt($ciphertext, $secretKey, $nonce, $aad); } catch (\\EasyWeChat\\Kernel\\Exceptions\\InvalidArgumentException $e) { \\Log::error('pay notification decrypt failed', ['err' => $e->getMessage(), 'key_len' => strlen($secretKey)]); return new \\Nyholm\\Psr7\\Response(500); // non-2xx makes WeChat redeliver }","preventionTips":["Rotate APIv3 keys together with app config in one deploy","Log key length (never the key) on failure","Never mix nonce/associated_data across notifications"],"tags":["php","openssl","aes-gcm","wechat-pay-v3","decryption","webhook"],"backgroundTag":"aes-gcm-decryption-failed","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}