fish2018/pansou · error
ciphertext too short
Error message
ciphertext too short
What it means
decryptPassword decrypts AES-256-GCM ciphertext produced by encryptPassword. The ciphertext is expected to be base64(nonce || sealed). After base64 decoding, if the byte slice is shorter than the GCM nonce size (12 bytes), there is no room for a nonce, so the function refuses with 'ciphertext too short' instead of slicing out of bounds. It signals that the stored string was never produced by encryptPassword (or was truncated/corrupted).
Solutions
- Verify the stored value is a non-truncated base64 blob at least 16 bytes long when decoded (12-byte nonce + tag); if not, the plaintext is unrecoverable — reset the password by re-running encryptPassword with the known plaintext.
- Check whether the value predates the encryption scheme (plaintext); migrate by re-encrypting: enc, _ := p.encryptPassword(storedPlaintext).
- Confirm decryptPassword and encryptPassword use the same key and the same base64 encoding (StdEncoding vs URLEncoding mismatch corrupts decode).
- Add a length check before calling decryptPassword to surface a clearer error.
Example fix
// before
password, err := p.decryptPassword(account.Password) // 'ciphertext too short' on plaintext value
// after
if dec, err := base64.StdEncoding.DecodeString(account.Password); err != nil || len(dec) < 12 {
// legacy/plaintext value: re-encrypt instead of decrypting
enc, encErr := p.encryptPassword(account.Password)
if encErr == nil { account.Password = enc }
}
password, err := p.decryptPassword(account.Password) Defensive patterns
Strategy: validation
Validate before calling
func isDecryptable(s string) bool {
raw, err := base64.StdEncoding.DecodeString(s)
return err == nil && len(raw) >= 12 // nonceSize for AES-GCM
}
// call before p.decryptPassword; if false, treat as legacy plaintext and re-encrypt Try / catch
password, err := p.decryptPassword(encrypted)
if err != nil {
if err.Error() == "ciphertext too short" {
// legacy plaintext value: re-encrypt in place
if enc, encErr := p.encryptPassword(encrypted); encErr == nil {
saveEncrypted(enc); return encrypted, nil
}
}
return "", err
} Prevention
- Always store passwords only via encryptPassword output; never write plaintext into the encrypted field.
- Migrate legacy plaintext rows once, at load time, by re-encrypting them.
- Use one base64 encoding variant consistently (StdEncoding) for write and read paths.
- Alert on rows whose decoded length < 16 bytes (nonce + minimum GCM tag).
When it happens
Trigger: decryptPassword is called with a stored password string that base64-decodes to fewer than 12 bytes — e.g. an empty string, a plaintext password stored instead of the encrypted form, a truncated DB field, or ciphertext corrupted/trimmed by storage.
Common situations: Legacy accounts whose passwords were stored in plaintext before encryption was introduced; manual DB edits; a migration that re-encoded the base64; copy-paste dropping characters from the stored value.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/389377c2e1ae7d22.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/gying/gying.go:1454
// base64解码
ciphertext, err := base64.StdEncoding.DecodeString(encrypted)
if err != nil {
return "", err
}
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return "", fmt.Errorf("ciphertext too short")
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
return string(plaintext), nil
}
// ============ Cookie管理 ============
// ============ Cookie 与反爬处理 ============
// getScraperClient 通过反射拿到 cloudscraper 内部的 http.Client,
// 便于读取和回写 cookie jar。
func getScraperClient(scraper *cloudscraper.Scraper) (*http.Client, error) {View on GitHub (pinned to beaa561337)