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

  1. Verify the input is the full Base64 token exactly as returned by the SDSO site (no truncation, whitespace or HTML entity mangling).
  2. Re-fetch a fresh encrypted URL from the SDSO site — stale cached values from an older site format will not decrypt.
  3. Check len(base64.StdEncoding.DecodeString(encryptedURL)) % 16 == 0 before calling, and log the actual length to diagnose truncation.
  4. 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

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)