{"record":{"id":"9bcd2e76f4fdc55a","repo":"chenhg5/cc-connect","slug":"aes-key-must-be-16-bytes-got-d","errorCode":null,"errorMessage":"aes key must be 16 bytes, got %d","messagePattern":"aes key must be 16 bytes, got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/weixin/cdn.go","lineNumber":58,"sourceCode":"func pkcs7Unpad(b []byte, blockSize int) ([]byte, error) {\n\tif len(b) == 0 || len(b)%blockSize != 0 {\n\t\treturn nil, fmt.Errorf(\"invalid padded length %d\", len(b))\n\t}\n\tn := int(b[len(b)-1])\n\tif n == 0 || n > blockSize || n > len(b) {\n\t\treturn nil, fmt.Errorf(\"invalid pkcs7 padding\")\n\t}\n\tfor i := len(b) - n; i < len(b); i++ {\n\t\tif b[i] != byte(n) {\n\t\t\treturn nil, fmt.Errorf(\"invalid pkcs7 padding\")\n\t\t}\n\t}\n\treturn b[:len(b)-n], nil\n}\n\nfunc encryptAESECB(plaintext, key []byte) ([]byte, error) {\n\tif len(key) != 16 {\n\t\treturn nil, fmt.Errorf(\"aes key must be 16 bytes, got %d\", len(key))\n\t}\n\tblock, err := aes.NewCipher(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpadded := pkcs7Pad(plaintext, aes.BlockSize)\n\tout := make([]byte, len(padded))\n\tfor i := 0; i < len(padded); i += aes.BlockSize {\n\t\tblock.Encrypt(out[i:i+aes.BlockSize], padded[i:i+aes.BlockSize])\n\t}\n\treturn out, nil\n}\n\nfunc decryptAESECB(ciphertext, key []byte) ([]byte, error) {\n\tif len(key) != 16 {\n\t\treturn nil, fmt.Errorf(\"aes key must be 16 bytes, got %d\", len(key))\n\t}\n\tif len(ciphertext)%aes.BlockSize != 0 {","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/weixin/cdn.go#L40-L76","documentation":"encryptAESECB in the Weixin CDN uploader requires exactly a 16-byte (AES-128) key; any other length returns 'aes key must be 16 bytes, got %d'. The key comes from the WeChat Work CDN flow and must be exactly the AES-128 key the API expects. This is a pre-flight guard before aes.NewCipher so callers get a clear message instead of a crypto error.","triggerScenarios":"Calling uploadBufferToCDN with a key decoded from a 24- or 32-byte base64 value (AES-192/256 key) or a raw hex string (32 ASCII chars = 32 bytes) instead of the 16 raw bytes; passing an empty/nil key; parsing aes_key incorrectly (not base64-decoding, or not unwrapping the hex-ASCII form).","commonSituations":"WeChat Work CDN aes_key arrives base64-encoded; if a developer decodes the hex-wrapped variant wrongly (keeps 32 hex chars) the key length check fires; config mistakes when hardcoding keys.","solutions":["Parse the key with the adapter's parseAesKey helper, which accepts base64(raw 16) and base64(hex-ASCII 32) and returns exactly 16 bytes","If you have a 32-char hex string, hex.DecodeString it to get 16 raw bytes before encrypting","Check len(key)==16 before calling uploadBufferToCDN and regenerate/obtain the key from the CDN API if not","Never pass hex/base64 text directly as the key bytes"],"exampleFix":"// before\nkey := []byte(media.AesKey) // still base64 text, wrong length\n// after\nkey, err := parseAesKey(media.AesKey, media.URL)\nif err != nil { return err }\nenc, err := encryptAESECB(plaintext, key) // key is exactly 16 bytes","handlingStrategy":"validation","validationCode":"if len(key) != 16 {\n    return fmt.Errorf(\"upload: need 16-byte AES key, got %d\", len(key))\n}\nenc, err := encryptAESECB(plaintext, key)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always derive the key via parseAesKey, never by hand-decoding base64","Remember WeChat Work CDN uses AES-128: keys are exactly 16 raw bytes","If the key is 32 hex chars, hex-decode to 16 bytes before use","Add a unit test asserting len(key)==16 for your key-parsing path"],"tags":["weixin","crypto","aes","key-length","upload"],"backgroundTag":"invalid-argument-value","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}