add reduced symbol set

This commit is contained in:
2025-12-11 10:59:02 +11:00
parent 454a494d30
commit 23f8f093a5

151
main.go
View File

@@ -22,8 +22,11 @@ type WordData struct {
var words WordData var words WordData
// Fixed symbol alphabet // Symbol alphabets
const symbolAlphabet = "!@#$%^&*()-+',?" const (
fullSymbolAlphabet = "!@#$%^&*()-+',?"
reducedSymbolAlphabet = "@#!"
)
type PageData struct { type PageData struct {
// Form values // Form values
@@ -32,6 +35,7 @@ type PageData struct {
MaxLen int MaxLen int
NumLen int NumLen int
SymbolCount int // symbols per separator SymbolCount int // symbols per separator
SymbolSet string // "full" or "reduced"
RandCaps string RandCaps string
// Stats // Stats
@@ -47,58 +51,6 @@ type PageData struct {
GeneratedPhrases []string GeneratedPhrases []string
} }
// commonLogMiddleware logs requests in Apache/Nginx "Common Log Format"
func commonLogMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Wrap ResponseWriter so we can capture status/size
lrw := &loggingResponseWriter{ResponseWriter: w, status: 200}
start := time.Now()
next.ServeHTTP(lrw, r)
duration := time.Since(start)
// Determine client IP
ip := r.RemoteAddr
if xf := r.Header.Get("X-Forwarded-For"); xf != "" {
ip = xf
}
// Common Log Format:
// 127.0.0.1 - - [10/Oct/2000:13:55:36 -0700] "GET /path HTTP/1.1" 200 2326
log.Printf(`%s - - [%s] "%s %s %s" %d %d "%s" "%s" %v`,
ip,
time.Now().Format("02/Jan/2006:15:04:05 -0700"),
r.Method,
r.RequestURI,
r.Proto,
lrw.status,
lrw.bytes,
r.Referer(),
r.UserAgent(),
duration,
)
})
}
// loggingResponseWriter allows us to capture status code & bytes written
type loggingResponseWriter struct {
http.ResponseWriter
status int
bytes int
}
func (lrw *loggingResponseWriter) WriteHeader(code int) {
lrw.status = code
lrw.ResponseWriter.WriteHeader(code)
}
func (lrw *loggingResponseWriter) Write(b []byte) (int, error) {
n, err := lrw.ResponseWriter.Write(b)
lrw.bytes += n
return n, err
}
// Single-file HTML template // Single-file HTML template
var pageTmpl = template.Must(template.New("page").Parse(` var pageTmpl = template.Must(template.New("page").Parse(`
<!doctype html> <!doctype html>
@@ -150,6 +102,17 @@ var pageTmpl = template.Must(template.New("page").Parse(`
<label for="symcount">Symbols per separator</label> <label for="symcount">Symbols per separator</label>
<input type="number" id="symcount" name="symcount" min="0" value="{{.SymbolCount}}"> <input type="number" id="symcount" name="symcount" min="0" value="{{.SymbolCount}}">
</div> </div>
<div>
<label for="symset">Symbol set</label>
<select id="symset" name="symset">
<option value="full" {{if or (eq .SymbolSet "") (eq .SymbolSet "full")}}selected{{end}}>
Full (!@#$%^&*()-+',?)
</option>
<option value="reduced" {{if eq .SymbolSet "reduced"}}selected{{end}}>
Reduced (@#!)
</option>
</select>
</div>
<div> <div>
<label for="randcaps">Capitalization</label> <label for="randcaps">Capitalization</label>
<select id="randcaps" name="randcaps"> <select id="randcaps" name="randcaps">
@@ -218,6 +181,7 @@ func main() {
words = wd words = wd
log.Printf("Loaded %d distinct word lengths", len(words.ByLen)) log.Printf("Loaded %d distinct word lengths", len(words.ByLen))
// Wrap handler with common log middleware
http.Handle("/", commonLogMiddleware(http.HandlerFunc(handlePassphrase))) http.Handle("/", commonLogMiddleware(http.HandlerFunc(handlePassphrase)))
port := os.Getenv("PORT") port := os.Getenv("PORT")
@@ -265,14 +229,18 @@ func loadWordlist(path string) (WordData, error) {
func handlePassphrase(w http.ResponseWriter, r *http.Request) { func handlePassphrase(w http.ResponseWriter, r *http.Request) {
wordcount := parseIntQuery(r, "wordcount", 3) wordcount := parseIntQuery(r, "wordcount", 3)
minlen := parseIntQuery(r, "minlen", 5) minlen := parseIntQuery(r, "minlen", 1)
maxlen := parseIntQuery(r, "maxlen", 8) maxlen := parseIntQuery(r, "maxlen", 8)
numlen := parseIntQuery(r, "numlen", 1) numlen := parseIntQuery(r, "numlen", 2)
symbolCount := parseIntQuery(r, "symcount", 1) symbolCount := parseIntQuery(r, "symcount", 1)
randcaps := r.URL.Query().Get("randcaps") randcaps := r.URL.Query().Get("randcaps")
if randcaps == "" { if randcaps == "" {
randcaps = "first" randcaps = "first"
} }
symbolSet := r.URL.Query().Get("symset")
if symbolSet == "" {
symbolSet = "full"
}
if minlen < 1 { if minlen < 1 {
minlen = 1 minlen = 1
@@ -296,6 +264,7 @@ func handlePassphrase(w http.ResponseWriter, r *http.Request) {
MaxLen: maxlen, MaxLen: maxlen,
NumLen: numlen, NumLen: numlen,
SymbolCount: symbolCount, SymbolCount: symbolCount,
SymbolSet: symbolSet,
RandCaps: randcaps, RandCaps: randcaps,
} }
@@ -310,6 +279,17 @@ func handlePassphrase(w http.ResponseWriter, r *http.Request) {
avgLen := float64(totalLen) / float64(totalWords) avgLen := float64(totalLen) / float64(totalWords)
wordBits := math.Log2(float64(totalWords)) wordBits := math.Log2(float64(totalWords))
// Determine symbol alphabet based on dropdown & symbolCount
symAlphabet := ""
if symbolCount > 0 {
if symbolSet == "reduced" {
symAlphabet = reducedSymbolAlphabet
} else {
symAlphabet = fullSymbolAlphabet
}
}
symAlphabetSize := len(symAlphabet)
// Caps // Caps
var capBitsPerWord float64 var capBitsPerWord float64
var capMult float64 var capMult float64
@@ -329,7 +309,7 @@ func handlePassphrase(w http.ResponseWriter, r *http.Request) {
} }
// Separators: after every word if any digits or symbols are enabled // Separators: after every word if any digits or symbols are enabled
segmentsEnabled := (numlen > 0 || symbolCount > 0) segmentsEnabled := (numlen > 0 || (symbolCount > 0 && symAlphabetSize > 0))
segments := 0 segments := 0
if segmentsEnabled { if segmentsEnabled {
segments = wordcount segments = wordcount
@@ -338,7 +318,7 @@ func handlePassphrase(w http.ResponseWriter, r *http.Request) {
// Bits per separator segment // Bits per separator segment
separatorBitsPerSegment := 0.0 separatorBitsPerSegment := 0.0
if segmentsEnabled { if segmentsEnabled {
separatorBitsPerSegment = separatorBits(numlen, symbolCount, len(symbolAlphabet)) separatorBitsPerSegment = separatorBits(numlen, symbolCount, symAlphabetSize)
} }
// Total bits // Total bits
@@ -350,7 +330,7 @@ func handlePassphrase(w http.ResponseWriter, r *http.Request) {
combosCaps := math.Pow(capMult, float64(wordcount)) combosCaps := math.Pow(capMult, float64(wordcount))
segmentCombos := 1.0 segmentCombos := 1.0
if segmentsEnabled { if segmentsEnabled {
segmentCombos = math.Pow(separatorCombos(numlen, symbolCount, len(symbolAlphabet)), float64(segments)) segmentCombos = math.Pow(separatorCombos(numlen, symbolCount, symAlphabetSize), float64(segments))
} }
combos := combosPerWord * combosCaps * segmentCombos combos := combosPerWord * combosCaps * segmentCombos
@@ -367,7 +347,7 @@ func handlePassphrase(w http.ResponseWriter, r *http.Request) {
data.EffectiveBits = int(passBits + 0.5) data.EffectiveBits = int(passBits + 0.5)
data.TotalCombinations = combosStr data.TotalCombinations = combosStr
data.GeneratedPhrases = generatePassphrases(usewords, wordcount, numlen, symbolCount, randcaps) data.GeneratedPhrases = generatePassphrases(usewords, wordcount, numlen, symbolCount, symAlphabet, randcaps)
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := pageTmpl.Execute(w, data); err != nil { if err := pageTmpl.Execute(w, data); err != nil {
@@ -433,13 +413,13 @@ func separatorBits(numlen, symCountPerSeg, symAlphabetSize int) float64 {
} }
// generatePassphrases builds N phrases with separators after each word when enabled. // generatePassphrases builds N phrases with separators after each word when enabled.
func generatePassphrases(usewords []string, wordcount, numlen, symCountPerSeg int, randcaps string) []string { func generatePassphrases(usewords []string, wordcount, numlen, symCountPerSeg int, symAlphabet string, randcaps string) []string {
const numPhrases = 10 const numPhrases = 10
if len(usewords) == 0 { if len(usewords) == 0 {
return nil return nil
} }
segmentsEnabled := (numlen > 0 || symCountPerSeg > 0) segmentsEnabled := (numlen > 0 || (symCountPerSeg > 0 && len(symAlphabet) > 0))
symRunes := []rune(symbolAlphabet) symRunes := []rune(symAlphabet)
phrases := make([]string, 0, numPhrases) phrases := make([]string, 0, numPhrases)
for i := 0; i < numPhrases; i++ { for i := 0; i < numPhrases; i++ {
@@ -593,3 +573,48 @@ func commafyInt(n int64) string {
} }
return sign + strings.Join(parts, ",") return sign + strings.Join(parts, ",")
} }
// commonLogMiddleware logs requests in Apache/Nginx-style log format
func commonLogMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
lrw := &loggingResponseWriter{ResponseWriter: w, status: 200}
start := time.Now()
next.ServeHTTP(lrw, r)
duration := time.Since(start)
ip := r.RemoteAddr
if xf := r.Header.Get("X-Forwarded-For"); xf != "" {
ip = xf
}
log.Printf(`%s - - [%s] "%s %s %s" %d %d "%s" "%s" %v`,
ip,
time.Now().Format("02/Jan/2006:15:04:05 -0700"),
r.Method,
r.RequestURI,
r.Proto,
lrw.status,
lrw.bytes,
r.Referer(),
r.UserAgent(),
duration,
)
})
}
type loggingResponseWriter struct {
http.ResponseWriter
status int
bytes int
}
func (lrw *loggingResponseWriter) WriteHeader(code int) {
lrw.status = code
lrw.ResponseWriter.WriteHeader(code)
}
func (lrw *loggingResponseWriter) Write(b []byte) (int, error) {
n, err := lrw.ResponseWriter.Write(b)
lrw.bytes += n
return n, err
}