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

  1. Ensure the Encrypt (or echostr) parameter is the full base64 string WeCom sent, not truncated or trimmed
  2. Verify you are base64-decoding the correct field; decode the encrypted payload before decrypt, never a signature string
  3. Log len(ciphertext) at the call site and compare against what WeCom actually posted
  4. 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

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


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/f513da6a8211e646. Report an issue: GitHub.