{"record":{"id":"d5673c26ab5600f3","repo":"chenhg5/cc-connect","slug":"ciphertext-length-d-not-aligned-to-block","errorCode":null,"errorMessage":"ciphertext length %d not aligned to block","messagePattern":"ciphertext length (.+?) not aligned to block","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/weixin/cdn.go","lineNumber":77,"sourceCode":"\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 {\n\t\treturn nil, fmt.Errorf(\"ciphertext length %d not aligned to block\", len(ciphertext))\n\t}\n\tblock, err := aes.NewCipher(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tout := make([]byte, len(ciphertext))\n\tfor i := 0; i < len(ciphertext); i += aes.BlockSize {\n\t\tblock.Decrypt(out[i:i+aes.BlockSize], ciphertext[i:i+aes.BlockSize])\n\t}\n\treturn pkcs7Unpad(out, aes.BlockSize)\n}\n\n// parseAesKey decodes CDNMedia.aes_key: base64(raw 16 bytes) or base64(32-char hex ASCII) → 16 bytes.\nfunc parseAesKey(aesKeyBase64, label string) ([]byte, error) {\n\tdecoded, err := base64.StdEncoding.DecodeString(strings.TrimSpace(aesKeyBase64))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%s: aes_key base64: %w\", label, err)\n\t}","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/weixin/cdn.go#L59-L95","documentation":"After the key check, decryptAESECB verifies len(ciphertext)%aes.BlockSize==0; AES operates on 16-byte blocks and ECB mode cannot handle a partial final block, so a non-aligned ciphertext returns 'ciphertext length %d not aligned to block'. This almost always indicates an incomplete or corrupted download rather than a padding problem (padding is checked later by pkcs7Unpad).","triggerScenarios":"downloadAndDecryptCDN received a truncated body (connection dropped mid-download, Content-Length mismatch); the CDN returned a partial/error response; the caller sliced the ciphertext incorrectly before decrypting; empty ciphertext (len 0 is aligned, so this fires for lengths like 1..15 mod 16).","commonSituations":"Flaky network during large media download; proxy truncating the response; reading only part of resp.Body; retry logic that appended partial chunks.","solutions":["Re-download the CDN media and verify the byte count matches the expected file size before decrypting","Check HTTP status and Content-Length of the CDN response before ReadAll completes","Ensure the entire response body was read (io.ReadAll, no partial reads/slices)","If the payload is genuinely unaligned, the data is not valid AES-ECB ciphertext — do not attempt decryption"],"exampleFix":"data, err := io.ReadAll(resp.Body)\nif err != nil { return err }\nif resp.StatusCode != 200 || int64(len(data)) != resp.ContentLength {\n    return fmt.Errorf(\"cdn: incomplete download (%d/%d bytes)\", len(data), resp.ContentLength)\n}\nplain, err := decryptAESECB(data, key)","handlingStrategy":"validation","validationCode":"if len(data)%aes.BlockSize != 0 {\n    return fmt.Errorf(\"cdn: got %d bytes, not multiple of 16 — download incomplete\", len(data))\n}\nplain, err := decryptAESECB(data, key)","typeGuard":null,"tryCatchPattern":"if plain, err := decryptAESECB(data, key); err != nil {\n    if strings.Contains(err.Error(), \"not aligned to block\") {\n        return redownloadWithVerification(media) // truncated body\n    }\n    return err\n}","preventionTips":["Compare downloaded byte count to Content-Length and expected file size before decrypting","Use resumable/verified downloads for large media","Check resp.StatusCode == 200 and content type before treating the body as ciphertext","Avoid retry logic that appends partial bodies instead of replacing them"],"tags":["weixin","crypto","aes","download","truncated-data"],"backgroundTag":"shape-mismatch","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"}