123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- // Package password provides a library for generating high-entropy random
- // password strings via the crypto/rand package.
- //
- // res, err := Generate(64, 10, 10, false, false)
- // if err != nil {
- // log.Fatal(err)
- // }
- // log.Printf(res)
- //
- // Most functions are safe for concurrent use.
- package password
- import (
- "crypto/rand"
- "errors"
- "math/big"
- "strings"
- )
- const (
- // LowerLetters is the list of lowercase letters.
- LowerLetters = "abcdefghijklmnopqrstuvwxyz"
- // UpperLetters is the list of uppercase letters.
- UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- // Digits is the list of permitted digits.
- Digits = "0123456789"
- // Symbols is the list of symbols.
- Symbols = "~!@#$%^&*()_+`-={}|[]\\:\"<>?,./"
- )
- var (
- // ErrExceedsTotalLength is the error returned with the number of digits and
- // symbols is greater than the total length.
- ErrExceedsTotalLength = errors.New("number of digits and symbols must be less than total length")
- // ErrLettersExceedsAvailable is the error returned with the number of letters
- // exceeds the number of available letters and repeats are not allowed.
- ErrLettersExceedsAvailable = errors.New("number of letters exceeds available letters and repeats are not allowed")
- // ErrDigitsExceedsAvailable is the error returned with the number of digits
- // exceeds the number of available digits and repeats are not allowed.
- ErrDigitsExceedsAvailable = errors.New("number of digits exceeds available digits and repeats are not allowed")
- // ErrSymbolsExceedsAvailable is the error returned with the number of symbols
- // exceeds the number of available symbols and repeats are not allowed.
- ErrSymbolsExceedsAvailable = errors.New("number of symbols exceeds available symbols and repeats are not allowed")
- )
- // Generator is the stateful generator which can be used to customize the list
- // of letters, digits, and/or symbols.
- type Generator struct {
- lowerLetters string
- upperLetters string
- digits string
- symbols string
- }
- // GeneratorInput is used as input to the NewGenerator function.
- type GeneratorInput struct {
- LowerLetters string
- UpperLetters string
- Digits string
- Symbols string
- }
- // NewGenerator creates a new Generator from the specified configuration. If no
- // input is given, all the default values are used. This function is safe for
- // concurrent use.
- func NewGenerator(i *GeneratorInput) (*Generator, error) {
- if i == nil {
- i = new(GeneratorInput)
- }
- g := &Generator{
- lowerLetters: i.LowerLetters,
- upperLetters: i.UpperLetters,
- digits: i.Digits,
- symbols: i.Symbols,
- }
- if g.lowerLetters == "" {
- g.lowerLetters = LowerLetters
- }
- if g.upperLetters == "" {
- g.upperLetters = UpperLetters
- }
- if g.digits == "" {
- g.digits = Digits
- }
- if g.symbols == "" {
- g.symbols = Symbols
- }
- return g, nil
- }
- // Generate generates a password with the given requirements. length is the
- // total number of characters in the password. numDigits is the number of digits
- // to include in the result. numSymbols is the number of symbols to include in
- // the result. noUpper excludes uppercase letters from the results. allowRepeat
- // allows characters to repeat.
- //
- // The algorithm is fast, but it's not designed to be performant; it favors
- // entropy over speed. This function is safe for concurrent use.
- func (g *Generator) Generate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) (string, error) {
- letters := g.lowerLetters
- if !noUpper {
- letters += g.upperLetters
- }
- chars := length - numDigits - numSymbols
- if chars < 0 {
- return "", ErrExceedsTotalLength
- }
- if !allowRepeat && chars > len(letters) {
- return "", ErrLettersExceedsAvailable
- }
- if !allowRepeat && numDigits > len(g.digits) {
- return "", ErrDigitsExceedsAvailable
- }
- if !allowRepeat && numSymbols > len(g.symbols) {
- return "", ErrSymbolsExceedsAvailable
- }
- var result string
- // Characters
- for i := 0; i < chars; i++ {
- ch, err := randomElement(letters)
- if err != nil {
- return "", err
- }
- if !allowRepeat && strings.Contains(result, ch) {
- i--
- continue
- }
- result, err = randomInsert(result, ch)
- if err != nil {
- return "", err
- }
- }
- // Digits
- for i := 0; i < numDigits; i++ {
- d, err := randomElement(g.digits)
- if err != nil {
- return "", err
- }
- if !allowRepeat && strings.Contains(result, d) {
- i--
- continue
- }
- result, err = randomInsert(result, d)
- if err != nil {
- return "", err
- }
- }
- // Symbols
- for i := 0; i < numSymbols; i++ {
- sym, err := randomElement(g.symbols)
- if err != nil {
- return "", err
- }
- if !allowRepeat && strings.Contains(result, sym) {
- i--
- continue
- }
- result, err = randomInsert(result, sym)
- if err != nil {
- return "", err
- }
- }
- return result, nil
- }
- // MustGenerate is the same as Generate, but panics on error.
- func (g *Generator) MustGenerate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) string {
- res, err := g.Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
- if err != nil {
- panic(err)
- }
- return res
- }
- // See Generator.Generate for usage.
- func Generate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) (string, error) {
- gen, err := NewGenerator(nil)
- if err != nil {
- return "", err
- }
- return gen.Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
- }
- // See Generator.MustGenerate for usage.
- func MustGenerate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) string {
- res, err := Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
- if err != nil {
- panic(err)
- }
- return res
- }
- // randomInsert randomly inserts the given value into the given string.
- func randomInsert(s, val string) (string, error) {
- if s == "" {
- return val, nil
- }
- n, err := rand.Int(rand.Reader, big.NewInt(int64(len(s))))
- if err != nil {
- return "", err
- }
- i := n.Int64()
- return s[0:i] + val + s[i:len(s)], nil
- }
- // randomElement extracts a random element from the given string.
- func randomElement(s string) (string, error) {
- n, err := rand.Int(rand.Reader, big.NewInt(int64(len(s))))
- if err != nil {
- return "", err
- }
- return string(s[n.Int64()]), nil
- }
|