Tencent/WeKnora · error

decrypt file: %w

Error message

decrypt file: %w

What it means

DownloadFile fetched the AES-256-CBC encrypted media from the WeCom URL but decryptAESCBC failed while decrypting it with the message's aeskey. The underlying cause (bad base64, wrong key, corrupt data) is wrapped in this error. It indicates the media bytes cannot be recovered as-is.

Source

Thrown at internal/im/wecom/ws_adapter.go:120

	if aesKeyB64 == "" {
		// No encryption — return raw content (e.g. webhook mode uses media API)
		return reader, fileName, nil
	}

	// Read all encrypted content
	encryptedData, err := io.ReadAll(reader)
	reader.Close()
	if err != nil {
		return nil, "", fmt.Errorf("read encrypted file: %w", err)
	}

	logger.Debugf(ctx, "[WeCom] Decrypting file: name=%s encrypted_size=%d aes_key_len=%d",
		fileName, len(encryptedData), len(aesKeyB64))

	// Decrypt
	decrypted, err := decryptAESCBC(encryptedData, aesKeyB64)
	if err != nil {
		return nil, "", fmt.Errorf("decrypt file: %w", err)
	}

	logger.Debugf(ctx, "[WeCom] File decrypted: name=%s decrypted_size=%d", fileName, len(decrypted))

	return io.NopCloser(bytes.NewReader(decrypted)), fileName, nil
}

// decryptAESCBC decrypts data encrypted with AES-256-CBC using PKCS#7 padding.
// The aesKeyB64 is the base64-encoded AES key provided per-message by WeCom.
// IV is the first 16 bytes of the decoded AES key.
func decryptAESCBC(ciphertext []byte, aesKeyB64 string) ([]byte, error) {
	// WeCom's per-message aeskey is base64-encoded (43 chars → 32 bytes after decode)
	aesKey, err := base64.StdEncoding.DecodeString(aesKeyB64 + "=")
	if err != nil {
		// Try without padding
		aesKey, err = base64.RawStdEncoding.DecodeString(aesKeyB64)
		if err != nil {
			return nil, fmt.Errorf("base64 decode aes key: %w", err)

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Retry the download from the original URL to rule out truncation, then re-decrypt.
  2. Use the aeskey delivered in the SAME message as the FileKey — never pair a key with a different message's payload.
  3. Log len(encryptedData) and the base64 decode result; verify the key decodes to 16/24/32 bytes and padding is correct.
  4. Re-fetch the message if it came from a queue, since WeCom media URLs and keys are per-message and may expire.

Example fix

// before
decrypted, err := decryptAESCBC(encryptedData, staleKey)
// after
decrypted, err := decryptAESCBC(encryptedData, msg.AesKey) // key from same message
if err != nil {
    // re-download once before failing
}
Defensive patterns

Strategy: retry

Validate before calling

key, err := base64.StdEncoding.DecodeString(msg.AesKey)
if err != nil || len(key) < 16 {
    return fmt.Errorf("invalid aes key for %s", msg.FileName)
}

Type guard

func validAESKey(b64 string) bool {
    k, err := base64.StdEncoding.DecodeString(b64)
    return err == nil && (len(k) == 16 || len(k) == 24 || len(k) == 32)
}

Try / catch

rc, name, err := adapter.DownloadFile(ctx, msg)
if err != nil && strings.HasPrefix(err.Error(), "decrypt file:") {
    // re-download once, then surface decrypt failure with cause: err
    rc, name, err = adapter.DownloadFile(ctx, msg)
}

Prevention

When it happens

Trigger: decryptAESCBC returns an error inside DownloadFile — e.g. invalid base64 aes key, key/IV mismatch, ciphertext not a multiple of the block size, or bad PKCS#7 padding from truncated/corrupted downloads.

Common situations: Reusing a cached aeskey from a different message; partial network read producing truncated ciphertext; WeCom changing media encryption details; corrupted stored payload being re-processed.

Related errors


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