gastownhall/beads · error
ciphertext too short
Error message
ciphertext too short
What it means
decryptWithKey splits AES-GCM ciphertext into a nonce prefix and body, using gcm.NonceSize() (12 bytes) as the split point. This error is returned when the ciphertext is shorter than the nonce size, so no valid nonce+body structure exists. It means the stored value cannot possibly be a valid AES-GCM output from this library — it is truncated, empty-ish, or encrypted under some other scheme.
Source
Thrown at internal/storage/dolt/credentials.go:226
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
return gcm.Seal(nonce, nonce, []byte(plaintext), nil), nil
}
// decryptWithKey decrypts ciphertext using AES-GCM with the given key.
func decryptWithKey(encrypted []byte, key []byte) (string, error) {
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(encrypted) < nonceSize {
return "", fmt.Errorf("ciphertext too short")
}
nonce, ciphertext := encrypted[:nonceSize], encrypted[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
return string(plaintext), nil
}
// encryptPassword encrypts a password using AES-GCM with the store's credential key.
func (s *DoltStore) encryptPassword(password string) ([]byte, error) {
if password == "" {
return nil, nil
}
s.mu.RLock()
key := s.credentialKey
s.mu.RUnlock()
if key == nil {View on GitHub (pinned to 71377f2769)
Solutions
- Re-add the peer's credentials so the password is re-encrypted correctly (the stored value is unrecoverable if truncated)
- Check whether the row predates the AES-GCM scheme; if so, re-enter the password via the peer-add flow rather than trying to decrypt it
- Restore the affected federation_peers rows from a backup if corruption was accidental
- Ensure no tooling or scripts rewrite password_encrypted directly; only use bd's peer management commands
Defensive patterns
Strategy: type-guard
Validate before calling
func looksLikeAESGCM(blob []byte) bool { return len(blob) >= 12 } // nonce(12) + at least one ciphertext/tag byte Type guard
func isDecryptableCiphertext(encrypted []byte) bool {
const gcmNonceSize = 12
return len(encrypted) >= gcmNonceSize
} Try / catch
pwd, err := decryptPassword(encrypted)
if err != nil {
if strings.Contains(err.Error(), "ciphertext too short") {
// stored value is corrupt/foreign scheme — prompt to re-enter credentials
return reAddPeerCredentials(peerName)
}
return err
} Prevention
- Never modify password_encrypted values outside bd commands
- Back up the full federation_peers table, not partial rows
- Re-enter peer passwords after restoring from partial backups
- Migrate off pre-AES-GCM formats by re-adding peers once
When it happens
Trigger: decryptWithKey is called with encrypted bytes of length < 12: a truncated or corrupted password_encrypted value in federation_peers, a value written by a different/older encryption scheme without a nonce prefix, manual tampering with the column, or a test passing deliberately short ciphertext.
Common situations: Database rows copied or migrated between workspaces losing bytes; manual SQL edits to password_encrypted; values produced by a pre-AES-GCM legacy format; restoring partial backups; test harnesses feeding malformed input.
Related errors
- failed to re-encrypt password for peer %s: %w
- failed to generate id: %w
- httpapi: request id seed: %w
- parsing last_pull timestamp %q: %w
- identity: generate request nonce: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/6f4d2d54a9de7481.
Report an issue: GitHub.