Tencent/WeKnora · error
ciphertext too short
Error message
ciphertext too short
What it means
decrypt() validates that the AES-encrypted ciphertext from a WeCom callback is at least one 16-byte AES block long before attempting CBC decryption. Since AES-CBC output always consists of full blocks, any payload shorter than aes.BlockSize cannot be validly encrypted data, so the function rejects it early. This guards against truncated, empty, or malformed encrypted payloads before any crypto work is done.
Source
Thrown at internal/im/wecom/webhook_adapter.go:460
computed := fmt.Sprintf("%x", hash.Sum(nil))
return hmac.Equal([]byte(computed), []byte(signature))
}
// 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")
}View on GitHub (pinned to 988cbb0330)
Solutions
- Ensure the Encrypt (or echostr) parameter is the full base64 string WeCom sent, not truncated or trimmed
- Verify you are base64-decoding the correct field; decode the encrypted payload before decrypt, never a signature string
- Log len(ciphertext) at the call site and compare against what WeCom actually posted
- Enable the full message-mode callback in WeCom admin so Encrypt contains real encrypted data
Example fix
// before: passing raw signature instead of payload msg, err := adapter.ParseCallback(msgSignature, timestamp, nonce, sigString) // after: pass the base64 Encrypt field msg, err := adapter.ParseCallback(msgSignature, timestamp, nonce, encryptField)
Defensive patterns
Strategy: validation
Validate before calling
raw, _ := base64.StdEncoding.DecodeString(encryptParam)
if len(raw) < 16 { /* skip decrypt; log truncated payload */ } Type guard
func hasValidCiphertextLength(b []byte) bool { return len(b) >= 16 } Try / catch
msg, err := adapter.ParseCallback(sig, ts, nonce, enc)
if err != nil && strings.Contains(err.Error(), "ciphertext too short") {
logger.Warn("truncated WeCom callback payload")
return
} Prevention
- Pass the raw Encrypt parameter through untouched — no trimming or re-encoding
- Log payload length on decrypt failures
- Use WeCom's echostr verification flow in integration tests
When it happens
Trigger: Calling decrypt (indirectly via HandleURLVerification or ParseCallback) with an encrypted_msg / echostr string that is empty or shorter than 16 bytes after base64 decoding — e.g. a test harness posting a truncated or empty Encrypt field.
Common situations: Unit tests feeding synthetic callback payloads; a proxy or middleware stripping/trimming the Encrypt parameter; base64-decoding the wrong field so the decoded buffer is empty or tiny.
Related errors
- ciphertext length is not a multiple of AES block size
- invalid padding
- plaintext too short
- message length mismatch
- decrypt message: %w
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/f513da6a8211e646.
Report an issue: GitHub.