{"record":{"id":"865dc40d1049eac1","repo":"gastownhall/beads","slug":"failed-to-re-encrypt-password-for-peer-s-w","errorCode":null,"errorMessage":"failed to re-encrypt password for peer %s: %w","messagePattern":"failed to re-encrypt password for peer (.+?): %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/credentials.go","lineNumber":185,"sourceCode":"\t\t}\n\n\t\t// Decrypt with old key\n\t\tplaintext, err := decryptWithKey(encrypted, oldKey)\n\t\tif err != nil {\n\t\t\t// Can't decrypt with old key — skip (may already use a different scheme)\n\t\t\tcontinue\n\t\t}\n\t\ttoMigrate = append(toMigrate, migrationEntry{name: name, plaintext: plaintext})\n\t}\n\tif err := rows.Err(); err != nil {\n\t\treturn fmt.Errorf(\"failed to iterate peers for migration: %w\", err)\n\t}\n\n\t// Re-encrypt each password with the new key\n\tfor _, entry := range toMigrate {\n\t\tencrypted, err := encryptWithKey(entry.plaintext, newKey)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to re-encrypt password for peer %s: %w\", entry.name, err)\n\t\t}\n\t\tif _, err := s.execContext(ctx, `\n\t\t\tUPDATE federation_peers SET password_encrypted = ? WHERE name = ?\n\t\t`, encrypted, entry.name); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to update encrypted password for peer %s: %w\", entry.name, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// encryptWithKey encrypts plaintext using AES-GCM with the given key.\nfunc encryptWithKey(plaintext string, key []byte) ([]byte, error) {\n\tblock, err := aes.NewCipher(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgcm, err := cipher.NewGCM(block)","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/credentials.go#L167-L203","documentation":"When re-encrypting a peer password during migration, migrateCredentialKeys calls encryptWithKey(plaintext, newKey) with the freshly generated key. This error wraps an encryption failure for the named peer. Since the new key is always a valid 32-byte AES-256 key, this fails only when the nonce read from crypto/rand fails or the crypto setup errors — rare, but aborting is required to avoid storing a password encrypted in an unusable way.","triggerScenarios":"encryptWithKey fails while migrating a stored peer password: io.ReadFull(rand.Reader, nonce) errors (entropy source failure) inside encryptWithKey, or aes.NewCipher/cipher.NewGCM error (only if the new key were not 32 bytes, which cannot happen from the normal generation path).","commonSituations":"Entropy exhaustion or a broken CSPRNG in a container at boot (same root cause as key-generation failure); custom code paths or tests supplying a wrong-length key; crypto misconfiguration in exotic environments.","solutions":["Retry the operation — nonce-generation failures from crypto/rand are almost always transient","If it persists, fix the OS entropy source (check /dev/urandom, getrandom availability, container policies)","Restore the peer's plaintext password and re-add the peer (`bd` peer add) if migration left it un-migrated; the old ciphertext remains intact since the UPDATE never ran"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := bdCmd(); err != nil && strings.Contains(err.Error(), \"failed to re-encrypt password for peer\") {\n    return retryWithBackoff(bdCmd, 2) // entropy failures are transient\n}","preventionTips":["Ensure a healthy entropy source in production containers","Always let bd generate keys (never inject custom-length keys)","Keep peer credentials documented so they can be re-entered if migration aborts"],"tags":["crypto","encryption","aes-gcm","migration"],"backgroundTag":"aes-encrypt-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}