{"record":{"id":"0cdc3058959772cd","repo":"JeffreySu/WeiXinMPSDK","slug":"openhardwarecallbackcryptexception-decrypt-failed","errorCode":null,"errorMessage":"OpenHardwareCallbackCryptException (decrypt failed, WXBizMsgCrypt error code)","messagePattern":"OpenHardwareCallbackCryptException \\(decrypt failed, WXBizMsgCrypt error code\\)","errorType":"error_code","errorClass":"OpenHardwareCallbackCryptException","httpStatus":null,"severity":"error","filePath":"src/Senparc.Weixin.Work/Senparc.Weixin.Work/AdvancedAPIs/OpenHardware/OpenHardwareCallbackHandler.cs","lineNumber":70,"sourceCode":"            EnsureNotEmpty(nonce, nameof(nonce));\n            EnsureNotEmpty(encryptedBody, nameof(encryptedBody));\n\n            var envelope = JsonConvert\n                .DeserializeObject<OpenHardwareEncryptedCallbackRequest>(encryptedBody);\n            if (envelope == null || string.IsNullOrWhiteSpace(envelope.encrypt))\n            {\n                throw new ArgumentException(\n                    \"开放硬件回调正文必须包含非空 encrypt 字段。\",\n                    nameof(encryptedBody));\n            }\n\n            var plaintext = string.Empty;\n            var crypt = new WXBizMsgCrypt(token, encodingAesKey, receiveId);\n            var errorCode = crypt.DecryptJsonMsg(msgSignature, timestamp, nonce,\n                envelope.encrypt, ref plaintext);\n            if (errorCode != 0)\n            {\n                throw new OpenHardwareCallbackCryptException(errorCode);\n            }\n\n            return new OpenHardwareCallbackParseResult\n            {\n                tousername = envelope.tousername,\n                plaintext = plaintext,\n                message = ParsePlaintext(plaintext)\n            };\n        }\n\n        /// <summary>\n        /// 将已解密的开放硬件 JSON 按 event_type 或 command_type 分派为强类型消息。\n        /// </summary>\n        /// <param name=\"plaintext\">验签解密后的完整 JSON 文本。</param>\n        /// <returns>已识别的事件或指令；未识别类型会保留原始 JSON。</returns>\n        /// <exception cref=\"ArgumentException\">明文为空时抛出。</exception>\n        /// <exception cref=\"JsonReaderException\">明文不是合法 JSON 时抛出。</exception>\n        public static OpenHardwareCallbackMessageBase ParsePlaintext(","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/JeffreySu/WeiXinMPSDK/blob/be573f6f94bdbf718dd5f6cdecb137fbc7ff651e/src/Senparc.Weixin.Work/Senparc.Weixin.Work/AdvancedAPIs/OpenHardware/OpenHardwareCallbackHandler.cs#L52-L88","documentation":"After extracting the encrypt field, DecryptAndParse delegates AES decryption to WXBizMsgCrypt.DecryptJsonMsg. Any non-zero return code (signature mismatch, bad AES key, wrong receiveId, malformed ciphertext) is wrapped into OpenHardwareCallbackCryptException carrying the SDK error code. It means the callback could not be authenticated or decrypted with the configured credentials.","triggerScenarios":"Token or EncodingAESKey in code differs from those configured in the WeChat Work open-hardware app; receiveId (corpId/appId) mismatch; the msg_signature/timestamp/nonce from the request don't match the body (replayed or tampered callback); wrong secret copied from a different app.","commonSituations":"Rotating the AES key in the admin console but not in the app; multiple WeChat Work tenants posting to one endpoint with a hard-coded receiveId; message replaying after >5 minutes failing signature freshness checks.","solutions":["Compare the exception's error code with WXBizMsgCrypt docs: signature mismatch (invalid signature) → check token; decrypt failure → check EncodingAESKey; receiveId mismatch → check the receiveId parameter.","Copy Token/EncodingAESKey exactly from the open-hardware callback config page; avoid trailing whitespace or Base64 key truncation.","Make receiveId dynamic (resolve per-request from tousername/corpId) instead of hard-coding a single tenant.","Log timestamp/nonce and ensure callbacks are processed promptly; reject/retry stale messages cleanly.","Catch OpenHardwareCallbackCryptException and return a non-2xx only after verifying it is a genuine key mismatch, to avoid breaking WeChat's retry logic."],"exampleFix":"// before\nvar result = handler.DecryptAndParse(body, token, aesKey, \"corpX\"); // throws on key mismatch\n// after\ntry\n{\n    var result = handler.DecryptAndParse(body, token, aesKey, receiveId);\n}\ncatch (OpenHardwareCallbackCryptException ex)\n{\n    logger.LogWarning(ex, \"Callback decrypt failed, code {Code}\", ex.ErrorCode);\n    return Results.StatusCode(400);\n}","handlingStrategy":"try-catch","validationCode":"if (string.IsNullOrWhiteSpace(token) || string.IsNullOrWhiteSpace(encodingAesKey) || string.IsNullOrWhiteSpace(receiveId))\n    throw new InvalidOperationException(\"Callback token/AESKey/receiveId must be configured\");","typeGuard":"static bool CredentialsConfigured(CallbackOptions o) =>\n    !string.IsNullOrWhiteSpace(o.Token) && o.EncodingAesKey?.Length == 43 && !string.IsNullOrWhiteSpace(o.ReceiveId);","tryCatchPattern":"try { var result = handler.DecryptAndParse(body, token, aesKey, receiveId); }\ncatch (OpenHardwareCallbackCryptException ex)\n{\n    logger.LogWarning(\"Callback decrypt failed, WXBizMsgCrypt code {Code}\", ex.ErrorCode);\n    return Results.StatusCode(400);\n}","preventionTips":["Keep Token/EncodingAESKey in sync with the WeChat Work admin console; reload on rotation.","Resolve receiveId per request instead of hard-coding one tenant.","Handle signature freshness: process callbacks promptly and reject stale timestamps cleanly.","Log the SDK error code to distinguish token vs AESKey vs receiveId mismatches."],"tags":["callback","crypto","signature","open-hardware"],"backgroundTag":"checksum-mismatch","analyzedSha":"be573f6f94bdbf718dd5f6cdecb137fbc7ff651e","analyzedAt":"2026-09-12T10:01:50.733Z","contentChangedAt":"2026-09-12T10:01:50.733Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}