{"record":{"id":"389377c2e1ae7d22","repo":"fish2018/pansou","slug":"ciphertext-too-short","errorCode":null,"errorMessage":"ciphertext too short","messagePattern":"ciphertext too short","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/gying/gying.go","lineNumber":1454,"sourceCode":"\t// base64解码\n\tciphertext, err := base64.StdEncoding.DecodeString(encrypted)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tblock, err := aes.NewCipher(key)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tgcm, err := cipher.NewGCM(block)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tnonceSize := gcm.NonceSize()\n\tif len(ciphertext) < nonceSize {\n\t\treturn \"\", fmt.Errorf(\"ciphertext too short\")\n\t}\n\n\tnonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]\n\tplaintext, err := gcm.Open(nil, nonce, ciphertext, nil)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn string(plaintext), nil\n}\n\n// ============ Cookie管理 ============\n\n// ============ Cookie 与反爬处理 ============\n\n// getScraperClient 通过反射拿到 cloudscraper 内部的 http.Client，\n// 便于读取和回写 cookie jar。\nfunc getScraperClient(scraper *cloudscraper.Scraper) (*http.Client, error) {","sourceCodeStart":1436,"sourceCodeEnd":1472,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/gying/gying.go#L1436-L1472","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\npassword, err := p.decryptPassword(account.Password) // 'ciphertext too short' on plaintext value\n// after\nif dec, err := base64.StdEncoding.DecodeString(account.Password); err != nil || len(dec) < 12 {\n    // legacy/plaintext value: re-encrypt instead of decrypting\n    enc, encErr := p.encryptPassword(account.Password)\n    if encErr == nil { account.Password = enc }\n}\npassword, err := p.decryptPassword(account.Password)","handlingStrategy":"validation","validationCode":"func isDecryptable(s string) bool {\n    raw, err := base64.StdEncoding.DecodeString(s)\n    return err == nil && len(raw) >= 12 // nonceSize for AES-GCM\n}\n// call before p.decryptPassword; if false, treat as legacy plaintext and re-encrypt","typeGuard":null,"tryCatchPattern":"password, err := p.decryptPassword(encrypted)\nif err != nil {\n    if err.Error() == \"ciphertext too short\" {\n        // legacy plaintext value: re-encrypt in place\n        if enc, encErr := p.encryptPassword(encrypted); encErr == nil {\n            saveEncrypted(enc); return encrypted, nil\n        }\n    }\n    return \"\", err\n}","preventionTips":["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)."],"tags":["crypto","aes-gcm","decryption","data-corruption"],"backgroundTag":"ciphertext-too-short","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"}