fish2018/pansou · error
密文长度不是AES块大小的倍数
Error message
密文长度不是AES块大小的倍数
What it means
DecryptURL decrypts a Base64-encoded, AES-128-CBC encrypted URL returned by the SDSO site. Before decryption it validates that the decoded ciphertext length is a non-zero multiple of aes.BlockSize (16 bytes), because CBC mode can only process whole blocks. If the decoded payload length is not a multiple of 16, the function refuses to proceed with this error instead of producing garbage plaintext.
Solutions
- Verify the input is the full Base64 token exactly as returned by the SDSO site (no truncation, whitespace or HTML entity mangling).
- Re-fetch a fresh encrypted URL from the SDSO site — stale cached values from an older site format will not decrypt.
- Check len(base64.StdEncoding.DecodeString(encryptedURL)) % 16 == 0 before calling, and log the actual length to diagnose truncation.
- Confirm the site's encryption format (key/IV/mode) has not changed; update AESKey/AESIV or parsing logic if the upstream format changed.
Example fix
// before
plain, err := DecryptURL(strings.TrimSpace(item.Encrypted))
// after
cipherBytes, err := base64.StdEncoding.DecodeString(strings.TrimSpace(item.Encrypted))
if err != nil { return err }
if len(cipherBytes) == 0 || len(cipherBytes)%aes.BlockSize != 0 {
return fmt.Errorf("invalid ciphertext length %d for AES-CBC", len(cipherBytes))
}
plain, err := DecryptURL(item.Encrypted) Defensive patterns
Strategy: validation
Validate before calling
raw, err := base64.StdEncoding.DecodeString(enc)
if err != nil { return fmt.Errorf("not base64: %w", err) }
if len(raw) == 0 || len(raw)%16 != 0 {
return fmt.Errorf("ciphertext length %d not a multiple of AES block size 16", len(raw))
} Type guard
func isValidCiphertext(enc string) bool {
raw, err := base64.StdEncoding.DecodeString(enc)
return err == nil && len(raw) > 0 && len(raw)%16 == 0
} Prevention
- Always validate decoded length % 16 == 0 before AES-CBC operations.
- Never truncate or hand-edit Base64 tokens; copy them verbatim.
- Refresh encrypted URLs from the live site rather than long-lived caches.
- Detect upstream format changes early with a periodic decrypt smoke test.
When it happens
Trigger: Calling DecryptURL with a string whose base64.StdEncoding decoding yields a byte length not divisible by 16 — e.g. a truncated/older Base64 value, a plain (unencrypted) URL accidentally passed in, or data encrypted with a streaming/padding scheme that changed the payload size.
Common situations: The SDSO site changed its encryption format or key version so stored/old encrypted URLs no longer match; the encryptedURL was HTML-escaped or trimmed mid-string so base64 decoding truncated; a developer passes a raw http URL instead of the site's encrypted token.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/cd19003db35ddb09.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/sdso/sdso.go:396
func DecryptURL(encryptedURL string) (string, error) {
if encryptedURL == "" {
return "", fmt.Errorf("加密URL不能为空")
}
// Base64解码
ciphertext, err := base64.StdEncoding.DecodeString(encryptedURL)
if err != nil {
return "", fmt.Errorf("Base64解码失败: %w", err)
}
// 检查密文长度
if len(ciphertext) == 0 {
return "", fmt.Errorf("密文长度为0")
}
// 检查密文长度是否为16的倍数
if len(ciphertext)%aes.BlockSize != 0 {
return "", fmt.Errorf("密文长度不是AES块大小的倍数")
}
// 创建AES块加密器
block, err := aes.NewCipher([]byte(AESKey))
if err != nil {
return "", fmt.Errorf("创建AES加密器失败: %w", err)
}
// 创建CBC模式解密器
iv := []byte(AESIV)
if len(iv) != aes.BlockSize {
return "", fmt.Errorf("IV长度不正确: 期望%d,实际%d", aes.BlockSize, len(iv))
}
mode := cipher.NewCBCDecrypter(block, iv)
// 解密
plaintext := make([]byte, len(ciphertext))View on GitHub (pinned to beaa561337)