pwgen.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. #!/usr/bin/python3
  2. """generate word compound passwords
  3. usage: pwgen -c [number_of_phrases default=1]
  4. or pwgen [-s | -S | -n | -p | -/] [-L len default=20] [number_of_phrases default=1]
  5. """
  6. import random, sys
  7. from enum import Enum
  8. class Padding(Enum):
  9. spaces = 0
  10. dots = 1
  11. punct = 2
  12. numbers = 3
  13. camel = 4
  14. snake = 5
  15. class Config:
  16. def __init__(self):
  17. self.padding = Padding.spaces
  18. self.first_punct = False
  19. self.last_punct = False
  20. self.first_number = False
  21. self.last_number = False
  22. self.capitalize = False
  23. self.use_conjunctions = False
  24. self.target_len = 20 # default pw len
  25. self.number = 1
  26. def __repr__(self):
  27. return(str(list((repr(self.padding),
  28. self.first_punct, self.last_punct,
  29. self.first_number, self.last_number,
  30. self.capitalize,
  31. self.target_len, self.number,
  32. self.use_conjunctions))))
  33. SPACE = " "
  34. DOTS = ".,"
  35. PUNCTS = """ !@#$%^&*()-_=+[{]}\\|;:/?.>,<~"""
  36. NUMBERS = "0123456789"
  37. CONJUNCTIONS = ["and", "and a", "and the","or", "or the", "or a",
  38. "with a", "with", "with the", "by a", "by the",
  39. "on a", "on the","on", "in a", "in the", "in",
  40. "for", "for a", "for the"]
  41. # delete these
  42. number = 1
  43. targetLen = 20
  44. useConjunction = False
  45. add_punc = False
  46. add_number = False
  47. def toInt(s, chkRange, errStr = "sorry, that's not a number", rangeStr = "sorry, I can't do that number"):
  48. try:
  49. t = int(s)
  50. except:
  51. print (errStr)
  52. sys.exit()
  53. if t<chkRange[0] or t>chkRange[1]:
  54. print (rangeStr)
  55. sys.exit()
  56. return t
  57. def get_args(conf):
  58. narg = 1
  59. nargs = len(sys.argv)
  60. while narg<nargs:
  61. a = sys.argv[narg]
  62. if a=="-h" or a=="--help" or a=="--version":
  63. print ("pwgen.py v1.3 generate passphrase; inspired by xkcd/936 and SteveGibson\n")
  64. print ("""Usage: pwgen.py [-sSpPkKnNcCaMX] [-L length] [num_phrases]
  65. pwgen.py -h | --help | --version
  66. -s pad with only spaces (default; for smartphone)
  67. -S pad with period and comma (for smartphone)
  68. -p pad with special characters
  69. -P pad with numbers
  70. -k connect the words as CamelCase
  71. -K connect the words as snake_case
  72. -n add a number at the beginning
  73. -N add a number at the end
  74. -c add a special character at the begnning
  75. -C add a special character at the end
  76. -M capitalize the words
  77. -a connect the words with a conjuction filler
  78. -X same as -MNCS (capital, periods & commas, extra number & char)
  79. -L <n> make pass phrases at least this long; default=20
  80. num_phrases make multiple pass phrases, one per line; default=1\n""")
  81. sys.exit()
  82. elif a[0]=='-':
  83. for b in a[1:]:
  84. if b=='c':
  85. conf.first_punct = True
  86. elif b=="C":
  87. conf.last_punct = True
  88. elif b=="n":
  89. conf.first_number = True
  90. elif b=='N': # add number
  91. conf.last_number = True
  92. elif b=='s':
  93. conf.padding = Padding.spaces
  94. elif b=='S':
  95. conf.padding = Padding.dots
  96. elif b=='p':
  97. conf.padding = Padding.punct
  98. elif b=='P':
  99. conf.padding = Padding.numbers
  100. elif b=='k':
  101. conf.padding = Padding.camel
  102. elif b=='K':
  103. conf.padding = Padding.snake
  104. elif b=='M':
  105. conf.capitalize = True
  106. elif b=='X':
  107. conf.capitalize = True
  108. conf.last_punct = True
  109. conf.last_number = True
  110. conf.padding = Padding.dots
  111. elif b=="a":
  112. conf.use_conjunctions = True;
  113. elif b=="L":
  114. narg = narg + 1
  115. if narg < nargs:
  116. conf.target_len = toInt(sys.argv[narg],[12,66],rangeStr = "Sorry, I can't build phrases that long")
  117. else:
  118. if narg < nargs:
  119. conf.number = toInt(sys.argv[narg],[0,40],rangeStr = "Sorry, I can't print that many; try a smaller number") # pick off the number of phrases
  120. narg = narg + 1
  121. def load_words():
  122. f = open("/usr/share/dict/words")
  123. #f = open("/usr/share/dict/corncob_lowercase.txt")
  124. d = f.read().splitlines()
  125. f.close()
  126. d = list(filter(lambda x : len(x)<10 and not x.endswith("'s"), d))
  127. return d
  128. def get_word(conf, data):
  129. m = data[random.randint(0,len(data)-1)]
  130. if conf.capitalize or conf.padding == Padding.camel:
  131. m = m.capitalize()
  132. return m
  133. def pwgen(conf, data):
  134. w = [get_word(conf, data)]
  135. nExtraChars = conf.first_punct+conf.last_punct+conf.first_number+conf.last_number
  136. # TODO: we need a length estimator for this loop
  137. while sum(len(a) for a in w) + (len(w)-1 if not conf.padding==Padding.camel else 0) + nExtraChars < conf.target_len:
  138. if conf.use_conjunctions == True:
  139. c = CONJUNCTIONS[random.randint(0,len(CONJUNCTIONS)-1)]
  140. w.extend(c.split())
  141. w.append(get_word(conf, data))
  142. if w[-2] == 'a' and w[-1][0] in "aeiouAEIOU":
  143. w[-2] = "an"
  144. # change so that two-word conjunctions are multiple list elements
  145. if conf.padding == Padding.camel:
  146. ret = "".join(w)
  147. elif conf.padding == Padding.spaces:
  148. ret = SPACE.join(w)
  149. elif conf.padding == Padding.dots:
  150. p = DOTS[random.randint(0,len(DOTS)-1)]
  151. ret = p.join(w)
  152. elif conf.padding == Padding.numbers:
  153. n = NUMBERS[random.randint(0,len(NUMBERS)-1)]
  154. ret = n.join(w)
  155. elif conf.padding == Padding.punct:
  156. p = PUNCTS[random.randint(0,len(PUNCTS)-1)]
  157. ret = p.join(w)
  158. elif conf.padding == Padding.snake:
  159. ret = '_'.join(w)
  160. n = NUMBERS[random.randint(0,len(NUMBERS)-1)]
  161. if conf.first_number:
  162. ret = n + ret
  163. if conf.last_number:
  164. ret = ret + n
  165. p = PUNCTS[random.randint(0,len(PUNCTS)-1)]
  166. if conf.first_punct:
  167. ret = p + ret
  168. if conf.last_punct:
  169. ret = ret + p
  170. return ret
  171. if __name__ == "__main__":
  172. conf = Config()
  173. get_args(conf)
  174. data = load_words()
  175. for i in range(conf.number):
  176. pw = pwgen(conf, data)
  177. print(pw)
  178. """
  179. although this
  180. read n; i="1"; while [ $i -le $n ]; do cat /etc/dictionaries-common/words | shuf | head -1; i=$(( $i + 1 )); done
  181. will do the job almost as well
  182. NEW PLAN
  183. -s spaces (default)
  184. -S comma+period
  185. -p punctuation
  186. -P number
  187. -k CamelCase
  188. -K SnakeCase (only one of these 6 allowed)
  189. -c special char at beginning
  190. -C special char at end
  191. -n number at beginning
  192. -N number at end
  193. -M capitalize all words
  194. -a fill with conjunctions
  195. -X equivalent to -SCNM
  196. -L <n> length
  197. <n> number of samples
  198. -k -a not recommended
  199. -s dog cat mouse
  200. -S dog.cat.mouse
  201. -p dog#cat#mouse
  202. -P dog4cat4mouse
  203. -c !dog cat mouse
  204. -C dog cat mouse#
  205. -n 4dog cat mouse
  206. -N dog cat mouse4
  207. -cnCN $4dog cat mouse4$
  208. -M Dog Cat Mouse
  209. -k DogCatMouse
  210. -kN DogCatMouse2
  211. -K dog_cat_mouse
  212. -KMN Dog_Cat_Mouse3
  213. -a dog and a cat with mouse
  214. -Sa dog.and.a.cat.with.mouse
  215. -pa dog#and#a#cat#with#mouse
  216. -paCN dog^and^a^cat^with^mouse5%
  217. -X Dog.Cat.Mouse6&
  218. leet encoder a->4A e->3 i->I o->0 s->5$ t->7
  219. """