{"record":{"id":"87bf7102c70b2472","repo":"chenhg5/cc-connect","slug":"wecom-ws-invalid-pkcs7-pad-length-d","errorCode":null,"errorMessage":"wecom-ws: invalid pkcs7 pad length %d","messagePattern":"wecom-ws: invalid pkcs7 pad length (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/wecom/websocket_media.go","lineNumber":299,"sourceCode":"\tblock, err := aes.NewCipher(key32)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(ciphertext)%aes.BlockSize != 0 {\n\t\treturn nil, fmt.Errorf(\"wecom-ws: ciphertext not multiple of block size\")\n\t}\n\tplain := make([]byte, len(ciphertext))\n\tcipher.NewCBCDecrypter(block, iv).CryptBlocks(plain, ciphertext)\n\treturn pkcs7UnpadWeCom(plain)\n}\n\nfunc pkcs7UnpadWeCom(data []byte) ([]byte, error) {\n\tif len(data) == 0 {\n\t\treturn nil, fmt.Errorf(\"wecom-ws: empty padded data\")\n\t}\n\tpadLen := int(data[len(data)-1])\n\tif padLen < 1 || padLen > 32 || padLen > len(data) {\n\t\treturn nil, fmt.Errorf(\"wecom-ws: invalid pkcs7 pad length %d\", padLen)\n\t}\n\tfor i := len(data) - padLen; i < len(data); i++ {\n\t\tif int(data[i]) != padLen {\n\t\t\treturn nil, fmt.Errorf(\"wecom-ws: invalid pkcs7 padding\")\n\t\t}\n\t}\n\treturn data[:len(data)-padLen], nil\n}\n\nfunc parseContentDispositionFilename(h string) string {\n\th = strings.TrimSpace(h)\n\tif h == \"\" {\n\t\treturn \"\"\n\t}\n\tlower := strings.ToLower(h)\n\t// RFC 5987: filename*=UTF-8''percent-encoded\n\tif idx := strings.Index(lower, \"filename*=\"); idx >= 0 {\n\t\tval := strings.TrimSpace(h[idx+len(\"filename*=\"):])","sourceCodeStart":281,"sourceCodeEnd":317,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/wecom/websocket_media.go#L281-L317","documentation":"pkcs7UnpadWeCom reads the last byte as the PKCS7 pad length and validates it is between 1 and 32 (WeCom uses up to 32-byte padding) and not longer than the data. If the final byte is 0 or >32 (or exceeds the buffer), the padding is structurally impossible and the data is corrupt — usually a wrong AES key produced garbage plaintext.","triggerScenarios":"wecomDecryptFile decrypts with a wrong/mismatched AES key; the last plaintext byte is then a random value (often 0x00 or >32) failing the pad-length sanity check.","commonSituations":"EncodingAESKey rotated in the WeCom console but stale key in config; decrypting media from a different bot than the one whose key was configured; key decoded with wrong padding earlier.","solutions":["Verify the configured EncodingAESKey matches the bot that produced the media URL; re-sync after any key rotation.","Treat this as a wrong-key symptom: if it occurs consistently, the key bytes are wrong even though base64 decoding succeeded.","Confirm the media is WeCom AES-256-CBC with IV = first 16 key bytes (not a standard random-IV format)."],"exampleFix":"// before\nkey := loadOldKey() // rotated in console\nplain, err := wecomDecryptFile(ct, key) // fails: invalid pkcs7 pad length\n// after\nkey := loadKeyFromConsole() // re-fetched current EncodingAESKey\nplain, err := wecomDecryptFile(ct, key)","handlingStrategy":"try-catch","validationCode":"k, err := base64.StdEncoding.DecodeString(key)\nif err != nil || len(k) != 32 { /* fix config before runtime */ }","typeGuard":null,"tryCatchPattern":"plain, err := wecomDecryptFile(ct, key)\nif err != nil && strings.Contains(err.Error(), \"pkcs7\") {\n    return fmt.Errorf(\"decryption failed — likely wrong EncodingAESKey for this bot: %w\", err)\n}","preventionTips":["Keep the EncodingAESKey in sync with the WeCom console; re-sync after rotation.","Ensure the key used matches the bot that generated the media URL (multi-bot setups).","Run a known-answer decryption test at startup."],"tags":["wecom","crypto","pkcs7","wrong-key"],"backgroundTag":"checksum-mismatch","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}