...

Source file src/github.com/sethvargo/go-password/password/generate.go

Documentation: github.com/sethvargo/go-password/password

     1  // Package password provides a library for generating high-entropy random
     2  // password strings via the crypto/rand package.
     3  //
     4  //    res, err := Generate(64, 10, 10, false, false)
     5  //    if err != nil  {
     6  //      log.Fatal(err)
     7  //    }
     8  //    log.Printf(res)
     9  //
    10  // Most functions are safe for concurrent use.
    11  package password
    12  
    13  import (
    14  	"crypto/rand"
    15  	"errors"
    16  	"io"
    17  	"math/big"
    18  	"strings"
    19  )
    20  
    21  // Built-time checks that the generators implement the interface.
    22  var _ PasswordGenerator = (*Generator)(nil)
    23  
    24  // PasswordGenerator is an interface that implements the Generate function. This
    25  // is useful for testing where you can pass this interface instead of a real
    26  // password generator to mock responses for predicability.
    27  type PasswordGenerator interface {
    28  	Generate(int, int, int, bool, bool) (string, error)
    29  	MustGenerate(int, int, int, bool, bool) string
    30  }
    31  
    32  const (
    33  	// LowerLetters is the list of lowercase letters.
    34  	LowerLetters = "abcdefghijklmnopqrstuvwxyz"
    35  
    36  	// UpperLetters is the list of uppercase letters.
    37  	UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    38  
    39  	// Digits is the list of permitted digits.
    40  	Digits = "0123456789"
    41  
    42  	// Symbols is the list of symbols.
    43  	Symbols = "~!@#$%^&*()_+`-={}|[]\\:\"<>?,./"
    44  )
    45  
    46  var (
    47  	// ErrExceedsTotalLength is the error returned with the number of digits and
    48  	// symbols is greater than the total length.
    49  	ErrExceedsTotalLength = errors.New("number of digits and symbols must be less than total length")
    50  
    51  	// ErrLettersExceedsAvailable is the error returned with the number of letters
    52  	// exceeds the number of available letters and repeats are not allowed.
    53  	ErrLettersExceedsAvailable = errors.New("number of letters exceeds available letters and repeats are not allowed")
    54  
    55  	// ErrDigitsExceedsAvailable is the error returned with the number of digits
    56  	// exceeds the number of available digits and repeats are not allowed.
    57  	ErrDigitsExceedsAvailable = errors.New("number of digits exceeds available digits and repeats are not allowed")
    58  
    59  	// ErrSymbolsExceedsAvailable is the error returned with the number of symbols
    60  	// exceeds the number of available symbols and repeats are not allowed.
    61  	ErrSymbolsExceedsAvailable = errors.New("number of symbols exceeds available symbols and repeats are not allowed")
    62  )
    63  
    64  // Generator is the stateful generator which can be used to customize the list
    65  // of letters, digits, and/or symbols.
    66  type Generator struct {
    67  	lowerLetters string
    68  	upperLetters string
    69  	digits       string
    70  	symbols      string
    71  	reader       io.Reader
    72  }
    73  
    74  // GeneratorInput is used as input to the NewGenerator function.
    75  type GeneratorInput struct {
    76  	LowerLetters string
    77  	UpperLetters string
    78  	Digits       string
    79  	Symbols      string
    80  	Reader       io.Reader // rand.Reader by default
    81  }
    82  
    83  // NewGenerator creates a new Generator from the specified configuration. If no
    84  // input is given, all the default values are used. This function is safe for
    85  // concurrent use.
    86  func NewGenerator(i *GeneratorInput) (*Generator, error) {
    87  	if i == nil {
    88  		i = new(GeneratorInput)
    89  	}
    90  
    91  	g := &Generator{
    92  		lowerLetters: i.LowerLetters,
    93  		upperLetters: i.UpperLetters,
    94  		digits:       i.Digits,
    95  		symbols:      i.Symbols,
    96  		reader:       i.Reader,
    97  	}
    98  
    99  	if g.lowerLetters == "" {
   100  		g.lowerLetters = LowerLetters
   101  	}
   102  
   103  	if g.upperLetters == "" {
   104  		g.upperLetters = UpperLetters
   105  	}
   106  
   107  	if g.digits == "" {
   108  		g.digits = Digits
   109  	}
   110  
   111  	if g.symbols == "" {
   112  		g.symbols = Symbols
   113  	}
   114  
   115  	if g.reader == nil {
   116  		g.reader = rand.Reader
   117  	}
   118  
   119  	return g, nil
   120  }
   121  
   122  // Generate generates a password with the given requirements. length is the
   123  // total number of characters in the password. numDigits is the number of digits
   124  // to include in the result. numSymbols is the number of symbols to include in
   125  // the result. noUpper excludes uppercase letters from the results. allowRepeat
   126  // allows characters to repeat.
   127  //
   128  // The algorithm is fast, but it's not designed to be performant; it favors
   129  // entropy over speed. This function is safe for concurrent use.
   130  func (g *Generator) Generate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) (string, error) {
   131  	letters := g.lowerLetters
   132  	if !noUpper {
   133  		letters += g.upperLetters
   134  	}
   135  
   136  	chars := length - numDigits - numSymbols
   137  	if chars < 0 {
   138  		return "", ErrExceedsTotalLength
   139  	}
   140  
   141  	if !allowRepeat && chars > len(letters) {
   142  		return "", ErrLettersExceedsAvailable
   143  	}
   144  
   145  	if !allowRepeat && numDigits > len(g.digits) {
   146  		return "", ErrDigitsExceedsAvailable
   147  	}
   148  
   149  	if !allowRepeat && numSymbols > len(g.symbols) {
   150  		return "", ErrSymbolsExceedsAvailable
   151  	}
   152  
   153  	var result string
   154  
   155  	// Characters
   156  	for i := 0; i < chars; i++ {
   157  		ch, err := randomElement(g.reader, letters)
   158  		if err != nil {
   159  			return "", err
   160  		}
   161  
   162  		if !allowRepeat && strings.Contains(result, ch) {
   163  			i--
   164  			continue
   165  		}
   166  
   167  		result, err = randomInsert(g.reader, result, ch)
   168  		if err != nil {
   169  			return "", err
   170  		}
   171  	}
   172  
   173  	// Digits
   174  	for i := 0; i < numDigits; i++ {
   175  		d, err := randomElement(g.reader, g.digits)
   176  		if err != nil {
   177  			return "", err
   178  		}
   179  
   180  		if !allowRepeat && strings.Contains(result, d) {
   181  			i--
   182  			continue
   183  		}
   184  
   185  		result, err = randomInsert(g.reader, result, d)
   186  		if err != nil {
   187  			return "", err
   188  		}
   189  	}
   190  
   191  	// Symbols
   192  	for i := 0; i < numSymbols; i++ {
   193  		sym, err := randomElement(g.reader, g.symbols)
   194  		if err != nil {
   195  			return "", err
   196  		}
   197  
   198  		if !allowRepeat && strings.Contains(result, sym) {
   199  			i--
   200  			continue
   201  		}
   202  
   203  		result, err = randomInsert(g.reader, result, sym)
   204  		if err != nil {
   205  			return "", err
   206  		}
   207  	}
   208  
   209  	return result, nil
   210  }
   211  
   212  // MustGenerate is the same as Generate, but panics on error.
   213  func (g *Generator) MustGenerate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) string {
   214  	res, err := g.Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
   215  	if err != nil {
   216  		panic(err)
   217  	}
   218  	return res
   219  }
   220  
   221  // Generate is the package shortcut for Generator.Generate.
   222  func Generate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) (string, error) {
   223  	gen, err := NewGenerator(nil)
   224  	if err != nil {
   225  		return "", err
   226  	}
   227  
   228  	return gen.Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
   229  }
   230  
   231  // MustGenerate is the package shortcut for Generator.MustGenerate.
   232  func MustGenerate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) string {
   233  	res, err := Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
   234  	if err != nil {
   235  		panic(err)
   236  	}
   237  	return res
   238  }
   239  
   240  // randomInsert randomly inserts the given value into the given string.
   241  func randomInsert(reader io.Reader, s, val string) (string, error) {
   242  	if s == "" {
   243  		return val, nil
   244  	}
   245  
   246  	n, err := rand.Int(reader, big.NewInt(int64(len(s)+1)))
   247  	if err != nil {
   248  		return "", err
   249  	}
   250  	i := n.Int64()
   251  	return s[0:i] + val + s[i:], nil
   252  }
   253  
   254  // randomElement extracts a random element from the given string.
   255  func randomElement(reader io.Reader, s string) (string, error) {
   256  	n, err := rand.Int(reader, big.NewInt(int64(len(s))))
   257  	if err != nil {
   258  		return "", err
   259  	}
   260  	return string(s[n.Int64()]), nil
   261  }
   262  

View as plain text