package passgen import ( "crypto/rand" "errors" "fmt" "io" "math" ) const ( lowerChars = "abcdefghijklmnopqrstuvwxyz" upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" digitChars = "0123456789" symbolChars = "!@#$%^&*()-_=+[]{};:,.?/" ) var ErrEmptyAlphabet = errors.New("alphabet is empty") type Options struct { Length int UseLower bool UseUpper bool UseDigits bool UseSymbols bool Random io.Reader } func DefaultOptions() Options { return Options{ Length: 24, UseLower: true, UseUpper: true, UseDigits: true, UseSymbols: true, } } func Generate(opts Options) (string, error) { if opts.Length <= 0 { return "", fmt.Errorf("length must be positive") } alphabet := buildAlphabet(opts) if alphabet == "" { return "", ErrEmptyAlphabet } random := opts.Random if random == nil { random = rand.Reader } password := make([]byte, opts.Length) for i := range password { n, err := randomIndex(len(alphabet), random) if err != nil { return "", fmt.Errorf("choose password character: %w", err) } password[i] = alphabet[n] } return string(password), nil } func EntropyBits(length int, alphabetSize int) (float64, error) { if length <= 0 { return 0, fmt.Errorf("length must be positive") } if alphabetSize <= 1 { return 0, fmt.Errorf("alphabet size must be greater than 1") } return float64(length) * math.Log2(float64(alphabetSize)), nil } func AlphabetSize(opts Options) int { return len(buildAlphabet(opts)) } func buildAlphabet(opts Options) string { alphabet := "" if opts.UseLower { alphabet += lowerChars } if opts.UseUpper { alphabet += upperChars } if opts.UseDigits { alphabet += digitChars } if opts.UseSymbols { alphabet += symbolChars } return alphabet } func randomIndex(size int, random io.Reader) (int, error) { if size <= 0 { return 0, ErrEmptyAlphabet } if size > 256 { return 0, fmt.Errorf("alphabet size %d exceeds supported maximum 256", size) } cutoff := 256 - (256 % size) for { value, err := randomByte(random) if err != nil { return 0, err } if int(value) < cutoff { return int(value) % size, nil } } } func randomByte(random io.Reader) (byte, error) { var b [1]byte if _, err := io.ReadFull(random, b[:]); err != nil { return 0, fmt.Errorf("read random byte: %w", err) } return b[0], nil }