{"record":{"id":"c8cdcbd9e3c1f3af","repo":"w7corp/easywechat","slug":"40001","errorCode":"-40001","errorMessage":"Invalid Signature.","messagePattern":"Invalid Signature\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"src/Kernel/Encryptor.php","lineNumber":189,"sourceCode":"        $attributes = array_map(\n            static fn (string|int $attribute): string => (string) $attribute,\n            $attributes\n        );\n\n        sort($attributes, SORT_STRING);\n\n        return sha1(implode('', $attributes));\n    }\n\n    /**\n     * @throws RuntimeException\n     */\n    public function decrypt(string $ciphertext, string $msgSignature, string $nonce, int|string $timestamp): string\n    {\n        $signature = $this->createSignature($this->token, $timestamp, $nonce, $ciphertext);\n\n        if (! hash_equals($signature, $msgSignature)) {\n            throw new RuntimeException('Invalid Signature.', self::ERROR_INVALID_SIGNATURE);\n        }\n\n        $plaintext = Pkcs7::unpadding(\n            openssl_decrypt(\n                base64_decode($ciphertext, true) ?: '',\n                'aes-256-cbc',\n                $this->aesKey,\n                OPENSSL_NO_PADDING,\n                iv: substr($this->aesKey, 0, self::BLOCK_SIZE)\n            ) ?: '',\n            blockSize: strlen($this->aesKey)\n        );\n        $plaintext = substr($plaintext, self::BLOCK_SIZE);\n        $contentLength = (unpack('N', substr($plaintext, 0, 4)) ?: [])[1];\n\n        if ($this->receiveId && trim(substr($plaintext, $contentLength + 4)) !== $this->receiveId) {\n            throw new RuntimeException('Invalid appId.', self::ERROR_INVALID_APP_ID);\n        }","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/Kernel/Encryptor.php#L171-L207","documentation":"Encryptor::decrypt() recomputes the SHA1 signature over the sorted [token, timestamp, nonce, ciphertext] values and compares it with hash_equals against the msg_signature you supplied. Any mismatch throws RuntimeException('Invalid Signature.', -40001 ERROR_INVALID_SIGNATURE) before decryption is attempted — this is the WeChat callback authenticity check.","triggerScenarios":"Handling a WeChat callback with an Encryptor whose token differs from the Token configured in the MP/WeCom admin console; passing a timestamp/nonce taken from the wrong source (body vs query string); double-decoding or re-encoding the msg_signature/ciphertext from the query; or replaying a captured request through a different token.","commonSituations":"Token mismatch after rotating server config, multiple apps (test/prod) sharing one callback URL, framework middleware urldecoding query params twice, using the encrypted string from the body with the signature of a different field.","solutions":["Compare the token in your config with the Token field of the callback server config in the WeChat admin console — they must match exactly","Make sure timestamp, nonce and msg_signature all come from the same request's query string, and the ciphertext comes from that request's body, without re-encoding","Prefer $server->handle($request) / Server::handle() which extracts the four inputs from the PSR-7 request correctly instead of assembling them manually"],"exampleFix":"// before: manual assembly, easy to get wrong\n$plaintext = $encryptor->decrypt(\n    (string) $request->getContent(),\n    $request->query('msg_signature'),\n    $request->query('nonce'),\n    $request->query('timestamp')\n);\n\n// after: let the Server validate and decrypt\n$server = $app->getServer();\n$server->handle(function (array $message, \\asyWeChat\\ernel\\essage $msg) { /* ... */ });\n// inside a dedicated handler, token/nonce/timestamp are taken from the same request","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    $plaintext = $encryptor->decrypt($ciphertext, $msgSignature, $nonce, $timestamp);\n} catch (\\EasyWeChat\\Kernel\\Exceptions\\RuntimeException $e) {\n    if ((int) $e->getCode() === \\EasyWeChat\\Kernel\\Encryptor::ERROR_INVALID_SIGNATURE) {\n        // do NOT process the message; respond 403 and log for investigation\n        abort(403, 'Invalid callback signature');\n    }\n    throw $e;\n}","preventionTips":["Always take timestamp, nonce and msg_signature from the same request's query string and the ciphertext from its body","Prefer $app->getServer()->handle($request) over manual decrypt calls","When rotating the token in the admin console, deploy the matching config in the same window"],"tags":["php","easywechat","wechat-callback","signature","security","encryption"],"backgroundTag":"signature-verification-failed","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}