hub: use Hungarian word passphrases for retrieval passwords
Replace 64-char hex retrieval passwords with 5-word Hungarian passphrases (e.g. áldás-plazmid-palánta-süvítve-pócgém) for better UX in disaster recovery scenarios. Embed 29K+ word list via go:embed. API keys remain hex. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
package configgen
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
_ "embed"
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed hungarian.txt
|
||||
var hungarianWords string
|
||||
|
||||
// wordList is populated from the embedded hungarian.txt at init time.
|
||||
var wordList []string
|
||||
|
||||
func init() {
|
||||
seen := make(map[string]struct{}, 30000)
|
||||
for _, line := range strings.Split(hungarianWords, "\n") {
|
||||
w := strings.TrimSpace(line)
|
||||
if w == "" {
|
||||
continue
|
||||
}
|
||||
if _, dup := seen[w]; dup {
|
||||
continue
|
||||
}
|
||||
seen[w] = struct{}{}
|
||||
wordList = append(wordList, w)
|
||||
}
|
||||
}
|
||||
|
||||
// RandomPassphrase generates a human-friendly passphrase from Hungarian words.
|
||||
// Format: "szó-szó-szó-szó-szó" (words separated by dashes).
|
||||
// With a ~29K word list, 4 words gives ~59 bits of entropy, 5 words ~74 bits.
|
||||
// Easy to read, type, and dictate by Hungarian-speaking customers.
|
||||
func RandomPassphrase(wordCount int) (string, error) {
|
||||
words := make([]string, wordCount)
|
||||
max := big.NewInt(int64(len(wordList)))
|
||||
for i := range words {
|
||||
idx, err := rand.Int(rand.Reader, max)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
words[i] = wordList[idx.Int64()]
|
||||
}
|
||||
return strings.Join(words, "-"), nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package configgen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWordListNoDuplicates(t *testing.T) {
|
||||
seen := make(map[string]bool, len(wordList))
|
||||
for i, w := range wordList {
|
||||
if seen[w] {
|
||||
t.Errorf("duplicate word %q at index %d", w, i)
|
||||
}
|
||||
seen[w] = true
|
||||
}
|
||||
t.Logf("Total unique words: %d", len(seen))
|
||||
}
|
||||
|
||||
func TestWordListSize(t *testing.T) {
|
||||
if len(wordList) < 10000 {
|
||||
t.Errorf("word list too small: %d (want >= 10000)", len(wordList))
|
||||
}
|
||||
t.Logf("Word list size: %d", len(wordList))
|
||||
}
|
||||
|
||||
func TestRandomPassphrase(t *testing.T) {
|
||||
p, err := RandomPassphrase(5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
parts := strings.Split(p, "-")
|
||||
if len(parts) != 5 {
|
||||
t.Errorf("expected 5 words, got %d: %s", len(parts), p)
|
||||
}
|
||||
for _, word := range parts {
|
||||
if word == "" {
|
||||
t.Error("empty word in passphrase")
|
||||
}
|
||||
}
|
||||
t.Logf("Sample passphrase: %s", p)
|
||||
}
|
||||
|
||||
func TestRandomPassphraseSamples(t *testing.T) {
|
||||
for i := 0; i < 5; i++ {
|
||||
p, err := RandomPassphrase(5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Printf(" Sample %d: %s\n", i+1, p)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user