Tencent/WeKnora · error
ciphertext length is not a multiple of AES block size
Error message
ciphertext length is not a multiple of AES block size
What it means
decrypt() requires the ciphertext length to be an exact multiple of the 16-byte AES block size, as CBC mode demands. A length not aligned to the block size means the payload was corrupted, truncated, or was never AES-encrypted at all, so decryption is refused. There is a dedicated test (TestWebhookAdapterDecryptRejectsNonBlockAlignedCiphertext) asserting this behavior.
Source
Thrown at internal/im/wecom/webhook_adapter.go:463
}
// decrypt decrypts a WeCom AES-encrypted message.
func (a *WebhookAdapter) decrypt(encrypted string) ([]byte, error) {
ciphertext, err := base64.StdEncoding.DecodeString(encrypted)
if err != nil {
return nil, fmt.Errorf("base64 decode: %w", err)
}
block, err := aes.NewCipher(a.aesKey)
if err != nil {
return nil, fmt.Errorf("new cipher: %w", err)
}
if len(ciphertext) < aes.BlockSize {
return nil, fmt.Errorf("ciphertext too short")
}
if len(ciphertext)%aes.BlockSize != 0 {
return nil, fmt.Errorf("ciphertext length is not a multiple of AES block size")
}
iv := a.aesKey[:aes.BlockSize]
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(ciphertext, ciphertext)
// Remove and verify PKCS#7 padding
padLen := int(ciphertext[len(ciphertext)-1])
if padLen > wecomPKCS7BlockSize || padLen == 0 || padLen > len(ciphertext) {
return nil, fmt.Errorf("invalid padding")
}
for i := 0; i < padLen; i++ {
if ciphertext[len(ciphertext)-1-i] != byte(padLen) {
return nil, fmt.Errorf("invalid padding")
}
}
plaintext := ciphertext[:len(ciphertext)-padLen]
View on GitHub (pinned to 988cbb0330)
Solutions
- Check len(ciphertext)%16 after base64 decode; if not 0, the payload was corrupted in transit — log and request a fresh callback
- Do not trim, URL-decode twice, or whitespace-strip the base64 string beyond standard decoding
- Confirm you are not decrypting already-decrypted/plaintext content
- Re-run the WeCom callback verification flow to obtain a fresh valid echostr
Defensive patterns
Strategy: validation
Validate before calling
raw, err := base64.StdEncoding.DecodeString(encryptParam)
if err != nil || len(raw)%16 != 0 { /* reject before calling ParseCallback */ } Type guard
func isBlockAligned(b []byte) bool { return len(b) > 0 && len(b)%16 == 0 } Try / catch
msg, err := adapter.ParseCallback(sig, ts, nonce, enc)
if err != nil && strings.Contains(err.Error(), "multiple of AES block size") {
return fmt.Errorf("corrupted callback payload: %w", err)
} Prevention
- Never alter the base64 string between receipt and decrypt
- Enable msg_signature verification so corrupted payloads are rejected earlier
- Add a block-alignment unit test mirroring TestWebhookAdapterDecryptRejectsNonBlockAlignedCiphertext
When it happens
Trigger: Base64-decoded encrypted payload whose length % 16 != 0 — e.g. a test posting a non-block-aligned string, URL-encoding corruption dropping characters, or decrypting a plaintext string by mistake.
Common situations: Callback proxies re-encoding the Encrypt parameter; manual copy/paste of tokens losing trailing characters (base64 '=' padding); double-decoding the payload so length shrinks below block alignment.
Related errors
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/af045249dae333b9.
Report an issue: GitHub.