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
// Fixed symbol alphabet
const symbolAlphabet = "!@#$%^&*()-+',?"
// Symbol alphabets
const (
fullSymbolAlphabet = "!@#$%^&*()-+',?"
reducedSymbolAlphabet = "@#!"
)
type PageData struct {
// Form values
@@ -32,6 +35,7 @@ type PageData struct {
MaxLen int
NumLen int
SymbolCount int // symbols per separator
SymbolSet string // "full" or "reduced"
RandCaps string
// Stats
@@ -47,58 +51,6 @@ type PageData struct {
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
var pageTmpl = template.Must(template.New("page").Parse(`
<!doctype html>
@@ -150,6 +102,17 @@ var pageTmpl = template.Must(template.New("page").Parse(`
<label for="symcount">Symbols per separator</label>
<input type="number" id="symcount" name="symcount" min="0" value="{{.SymbolCount}}">
</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>
<label for="randcaps">Capitalization</label>
<select id="randcaps" name="randcaps">
@@ -218,6 +181,7 @@ func main() {
words = wd
log.Printf("Loaded %d distinct word lengths", len(words.ByLen))
// Wrap handler with common log middleware
http.Handle("/", commonLogMiddleware(http.HandlerFunc(handlePassphrase)))
port := os.Getenv("PORT")
@@ -265,14 +229,18 @@ func loadWordlist(path string) (WordData, error) {
func handlePassphrase(w http.ResponseWriter, r *http.Request) {
wordcount := parseIntQuery(r, "wordcount", 3)
minlen := parseIntQuery(r, "minlen", 5)
minlen := parseIntQuery(r, "minlen", 1)
maxlen := parseIntQuery(r, "maxlen", 8)
numlen := parseIntQuery(r, "numlen", 1)
numlen := parseIntQuery(r, "numlen", 2)
symbolCount := parseIntQuery(r, "symcount", 1)
randcaps := r.URL.Query().Get("randcaps")
if randcaps == "" {
randcaps = "first"
}
symbolSet := r.URL.Query().Get("symset")
if symbolSet == "" {
symbolSet = "full"
}
if minlen < 1 {
minlen = 1
@@ -296,6 +264,7 @@ func handlePassphrase(w http.ResponseWriter, r *http.Request) {
MaxLen: maxlen,
NumLen: numlen,
SymbolCount: symbolCount,
SymbolSet: symbolSet,
RandCaps: randcaps,
}
@@ -310,6 +279,17 @@ func handlePassphrase(w http.ResponseWriter, r *http.Request) {
avgLen := float64(totalLen) / 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
var capBitsPerWord 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
segmentsEnabled := (numlen > 0 || symbolCount > 0)
segmentsEnabled := (numlen > 0 || (symbolCount > 0 && symAlphabetSize > 0))
segments := 0
if segmentsEnabled {
segments = wordcount
@@ -338,7 +318,7 @@ func handlePassphrase(w http.ResponseWriter, r *http.Request) {
// Bits per separator segment
separatorBitsPerSegment := 0.0
if segmentsEnabled {
separatorBitsPerSegment = separatorBits(numlen, symbolCount, len(symbolAlphabet))
separatorBitsPerSegment = separatorBits(numlen, symbolCount, symAlphabetSize)
}
// Total bits
@@ -350,7 +330,7 @@ func handlePassphrase(w http.ResponseWriter, r *http.Request) {
combosCaps := math.Pow(capMult, float64(wordcount))
segmentCombos := 1.0
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
@@ -367,7 +347,7 @@ func handlePassphrase(w http.ResponseWriter, r *http.Request) {
data.EffectiveBits = int(passBits + 0.5)
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")
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.
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
if len(usewords) == 0 {
return nil
}
segmentsEnabled := (numlen > 0 || symCountPerSeg > 0)
symRunes := []rune(symbolAlphabet)
segmentsEnabled := (numlen > 0 || (symCountPerSeg > 0 && len(symAlphabet) > 0))
symRunes := []rune(symAlphabet)
phrases := make([]string, 0, numPhrases)
for i := 0; i < numPhrases; i++ {
@@ -593,3 +573,48 @@ func commafyInt(n int64) string {
}
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
}