generate.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  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. import (
  13. "crypto/rand"
  14. "errors"
  15. "math/big"
  16. "strings"
  17. )
  18. const (
  19. // LowerLetters is the list of lowercase letters.
  20. LowerLetters = "abcdefghijklmnopqrstuvwxyz"
  21. // UpperLetters is the list of uppercase letters.
  22. UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  23. // Digits is the list of permitted digits.
  24. Digits = "0123456789"
  25. // Symbols is the list of symbols.
  26. Symbols = "~!@#$%^&*()_+`-={}|[]\\:\"<>?,./"
  27. )
  28. var (
  29. // ErrExceedsTotalLength is the error returned with the number of digits and
  30. // symbols is greater than the total length.
  31. ErrExceedsTotalLength = errors.New("number of digits and symbols must be less than total length")
  32. // ErrLettersExceedsAvailable is the error returned with the number of letters
  33. // exceeds the number of available letters and repeats are not allowed.
  34. ErrLettersExceedsAvailable = errors.New("number of letters exceeds available letters and repeats are not allowed")
  35. // ErrDigitsExceedsAvailable is the error returned with the number of digits
  36. // exceeds the number of available digits and repeats are not allowed.
  37. ErrDigitsExceedsAvailable = errors.New("number of digits exceeds available digits and repeats are not allowed")
  38. // ErrSymbolsExceedsAvailable is the error returned with the number of symbols
  39. // exceeds the number of available symbols and repeats are not allowed.
  40. ErrSymbolsExceedsAvailable = errors.New("number of symbols exceeds available symbols and repeats are not allowed")
  41. )
  42. // Generator is the stateful generator which can be used to customize the list
  43. // of letters, digits, and/or symbols.
  44. type Generator struct {
  45. lowerLetters string
  46. upperLetters string
  47. digits string
  48. symbols string
  49. }
  50. // GeneratorInput is used as input to the NewGenerator function.
  51. type GeneratorInput struct {
  52. LowerLetters string
  53. UpperLetters string
  54. Digits string
  55. Symbols string
  56. }
  57. // NewGenerator creates a new Generator from the specified configuration. If no
  58. // input is given, all the default values are used. This function is safe for
  59. // concurrent use.
  60. func NewGenerator(i *GeneratorInput) (*Generator, error) {
  61. if i == nil {
  62. i = new(GeneratorInput)
  63. }
  64. g := &Generator{
  65. lowerLetters: i.LowerLetters,
  66. upperLetters: i.UpperLetters,
  67. digits: i.Digits,
  68. symbols: i.Symbols,
  69. }
  70. if g.lowerLetters == "" {
  71. g.lowerLetters = LowerLetters
  72. }
  73. if g.upperLetters == "" {
  74. g.upperLetters = UpperLetters
  75. }
  76. if g.digits == "" {
  77. g.digits = Digits
  78. }
  79. if g.symbols == "" {
  80. g.symbols = Symbols
  81. }
  82. return g, nil
  83. }
  84. // Generate generates a password with the given requirements. length is the
  85. // total number of characters in the password. numDigits is the number of digits
  86. // to include in the result. numSymbols is the number of symbols to include in
  87. // the result. noUpper excludes uppercase letters from the results. allowRepeat
  88. // allows characters to repeat.
  89. //
  90. // The algorithm is fast, but it's not designed to be performant; it favors
  91. // entropy over speed. This function is safe for concurrent use.
  92. func (g *Generator) Generate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) (string, error) {
  93. letters := g.lowerLetters
  94. if !noUpper {
  95. letters += g.upperLetters
  96. }
  97. chars := length - numDigits - numSymbols
  98. if chars < 0 {
  99. return "", ErrExceedsTotalLength
  100. }
  101. if !allowRepeat && chars > len(letters) {
  102. return "", ErrLettersExceedsAvailable
  103. }
  104. if !allowRepeat && numDigits > len(g.digits) {
  105. return "", ErrDigitsExceedsAvailable
  106. }
  107. if !allowRepeat && numSymbols > len(g.symbols) {
  108. return "", ErrSymbolsExceedsAvailable
  109. }
  110. var result string
  111. // Characters
  112. for i := 0; i < chars; i++ {
  113. ch, err := randomElement(letters)
  114. if err != nil {
  115. return "", err
  116. }
  117. if !allowRepeat && strings.Contains(result, ch) {
  118. i--
  119. continue
  120. }
  121. result, err = randomInsert(result, ch)
  122. if err != nil {
  123. return "", err
  124. }
  125. }
  126. // Digits
  127. for i := 0; i < numDigits; i++ {
  128. d, err := randomElement(g.digits)
  129. if err != nil {
  130. return "", err
  131. }
  132. if !allowRepeat && strings.Contains(result, d) {
  133. i--
  134. continue
  135. }
  136. result, err = randomInsert(result, d)
  137. if err != nil {
  138. return "", err
  139. }
  140. }
  141. // Symbols
  142. for i := 0; i < numSymbols; i++ {
  143. sym, err := randomElement(g.symbols)
  144. if err != nil {
  145. return "", err
  146. }
  147. if !allowRepeat && strings.Contains(result, sym) {
  148. i--
  149. continue
  150. }
  151. result, err = randomInsert(result, sym)
  152. if err != nil {
  153. return "", err
  154. }
  155. }
  156. return result, nil
  157. }
  158. // MustGenerate is the same as Generate, but panics on error.
  159. func (g *Generator) MustGenerate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) string {
  160. res, err := g.Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
  161. if err != nil {
  162. panic(err)
  163. }
  164. return res
  165. }
  166. // See Generator.Generate for usage.
  167. func Generate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) (string, error) {
  168. gen, err := NewGenerator(nil)
  169. if err != nil {
  170. return "", err
  171. }
  172. return gen.Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
  173. }
  174. // See Generator.MustGenerate for usage.
  175. func MustGenerate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) string {
  176. res, err := Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
  177. if err != nil {
  178. panic(err)
  179. }
  180. return res
  181. }
  182. // randomInsert randomly inserts the given value into the given string.
  183. func randomInsert(s, val string) (string, error) {
  184. if s == "" {
  185. return val, nil
  186. }
  187. n, err := rand.Int(rand.Reader, big.NewInt(int64(len(s))))
  188. if err != nil {
  189. return "", err
  190. }
  191. i := n.Int64()
  192. return s[0:i] + val + s[i:len(s)], nil
  193. }
  194. // randomElement extracts a random element from the given string.
  195. func randomElement(s string) (string, error) {
  196. n, err := rand.Int(rand.Reader, big.NewInt(int64(len(s))))
  197. if err != nil {
  198. return "", err
  199. }
  200. return string(s[n.Int64()]), nil
  201. }