{"record":{"id":"cd19003db35ddb09","repo":"fish2018/pansou","slug":"aes","errorCode":null,"errorMessage":"密文长度不是AES块大小的倍数","messagePattern":"密文长度不是AES块大小的倍数","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/sdso/sdso.go","lineNumber":396,"sourceCode":"func DecryptURL(encryptedURL string) (string, error) {\n\tif encryptedURL == \"\" {\n\t\treturn \"\", fmt.Errorf(\"加密URL不能为空\")\n\t}\n\n\t// Base64解码\n\tciphertext, err := base64.StdEncoding.DecodeString(encryptedURL)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"Base64解码失败: %w\", err)\n\t}\n\n\t// 检查密文长度\n\tif len(ciphertext) == 0 {\n\t\treturn \"\", fmt.Errorf(\"密文长度为0\")\n\t}\n\n\t// 检查密文长度是否为16的倍数\n\tif len(ciphertext)%aes.BlockSize != 0 {\n\t\treturn \"\", fmt.Errorf(\"密文长度不是AES块大小的倍数\")\n\t}\n\n\t// 创建AES块加密器\n\tblock, err := aes.NewCipher([]byte(AESKey))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"创建AES加密器失败: %w\", err)\n\t}\n\n\t// 创建CBC模式解密器\n\tiv := []byte(AESIV)\n\tif len(iv) != aes.BlockSize {\n\t\treturn \"\", fmt.Errorf(\"IV长度不正确: 期望%d，实际%d\", aes.BlockSize, len(iv))\n\t}\n\n\tmode := cipher.NewCBCDecrypter(block, iv)\n\n\t// 解密\n\tplaintext := make([]byte, len(ciphertext))","sourceCodeStart":378,"sourceCodeEnd":414,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/sdso/sdso.go#L378-L414","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nplain, err := DecryptURL(strings.TrimSpace(item.Encrypted))\n// after\ncipherBytes, err := base64.StdEncoding.DecodeString(strings.TrimSpace(item.Encrypted))\nif err != nil { return err }\nif len(cipherBytes) == 0 || len(cipherBytes)%aes.BlockSize != 0 {\n    return fmt.Errorf(\"invalid ciphertext length %d for AES-CBC\", len(cipherBytes))\n}\nplain, err := DecryptURL(item.Encrypted)","handlingStrategy":"validation","validationCode":"raw, err := base64.StdEncoding.DecodeString(enc)\nif err != nil { return fmt.Errorf(\"not base64: %w\", err) }\nif len(raw) == 0 || len(raw)%16 != 0 {\n    return fmt.Errorf(\"ciphertext length %d not a multiple of AES block size 16\", len(raw))\n}","typeGuard":"func isValidCiphertext(enc string) bool {\n    raw, err := base64.StdEncoding.DecodeString(enc)\n    return err == nil && len(raw) > 0 && len(raw)%16 == 0\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["crypto","aes","validation","go"],"backgroundTag":"invalid-argument-format","analyzedSha":"beaa56133755a548ebc51b090b3816e2ae044aa6","analyzedAt":"2026-09-07T00:31:18.025Z","contentChangedAt":"2026-09-07T00:31:18.025Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}