fork(8) download
  1. from __future__ import division, print_function
  2. from collections import namedtuple as _namedtuple
  3. from random import SystemRandom as _SystemRandom
  4. import argparse
  5. import getpass
  6. import hashlib
  7. import re
  8. import sys
  9.  
  10. _BASE64_CHARACTERS = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
  11. _SALT_RE = re.compile(r'\$(?P<algo>\d)\$(?:rounds=(?P<rounds>\d+)\$)?(?P<salt>.{1,16})')
  12. _ROUNDS_DEFAULT = 5000 # As used by crypt(3)
  13. _PY2 = sys.version_info < (3, 0, 0)
  14. _sr = _SystemRandom()
  15.  
  16.  
  17. class _Method(_namedtuple('_Method', 'name ident salt_chars total_size')):
  18. """Class representing a salt method per the Modular Crypt Format or the
  19. legacy 2-character crypt method."""
  20.  
  21. def __repr__(self):
  22. return '<crypt.METHOD_{0}>'.format(self.name)
  23.  
  24.  
  25. # available salting/crypto methods
  26. METHOD_SHA256 = _Method('SHA256', '5', 16, 63)
  27. METHOD_SHA512 = _Method('SHA512', '6', 16, 106)
  28.  
  29. methods = (
  30. METHOD_SHA512,
  31. METHOD_SHA256,
  32. )
  33.  
  34.  
  35. def mksalt(method=None, rounds=None):
  36. """Generate a salt for the specified method.
  37. If not specified, the strongest available method will be used.
  38. """
  39. if method is None:
  40. method = methods[0]
  41. salt = ['${0}$'.format(method.ident) if method.ident else '']
  42. if rounds:
  43. salt.append('rounds={0:d}$'.format(rounds))
  44. salt.append(''.join(_sr.choice(_BASE64_CHARACTERS) for char in range(method.salt_chars)))
  45. return ''.join(salt)
  46.  
  47.  
  48. def crypt(word, salt=None, rounds=_ROUNDS_DEFAULT):
  49. """Return a string representing the one-way hash of a password, with a salt
  50. prepended.
  51. If ``salt`` is not specified or is ``None``, the strongest
  52. available method will be selected and a salt generated. Otherwise,
  53. ``salt`` may be one of the ``crypt.METHOD_*`` values, or a string as
  54. returned by ``crypt.mksalt()``.
  55. """
  56. if salt is None or isinstance(salt, _Method):
  57. salt = mksalt(salt, rounds)
  58.  
  59. algo, rounds, salt = extract_components_from_salt(salt)
  60. if algo == 5:
  61. hashfunc = hashlib.sha256
  62. elif algo == 6:
  63. hashfunc = hashlib.sha512
  64. else:
  65. raise ValueError('Unsupported algorithm, must be either 5 (sha256) or 6 (sha512)')
  66.  
  67. return sha2_crypt(word, salt, hashfunc, rounds)
  68.  
  69.  
  70. def sha2_crypt(key, salt, hashfunc, rounds=_ROUNDS_DEFAULT):
  71. """
  72. This algorithm is insane. History can be found at
  73. https://e...content-available-to-author-only...a.org/wiki/Crypt_%28C%29
  74. """
  75. key = key.encode('utf-8')
  76. h = hashfunc()
  77. alt_h = hashfunc()
  78. digest_size = h.digest_size
  79. key_len = len(key)
  80.  
  81. # First, feed key, salt and then key again to the alt hash
  82. alt_h.update(key)
  83. alt_h.update(salt.encode('utf-8'))
  84. alt_h.update(key)
  85. alt_result = alt_h.digest()
  86.  
  87. # Feed key and salt to the primary hash
  88. h.update(key)
  89. h.update(salt.encode('utf-8'))
  90.  
  91. # Feed as many (loopping) bytes from alt digest as the length of the key
  92. for i in range(key_len//digest_size):
  93. h.update(alt_result)
  94. h.update(alt_result[:(key_len % digest_size)])
  95.  
  96. # Take the binary representation of the length of the key and for every
  97. # 1 add the alternate digest, for every 0 the key
  98. bits = key_len
  99. while bits > 0:
  100. if bits & 1 == 0:
  101. h.update(key)
  102. else:
  103. h.update(alt_result)
  104. bits >>= 1
  105.  
  106. # Store the results from the primary hash
  107. alt_result = h.digest()
  108.  
  109. h = hashfunc()
  110.  
  111. # Add password for each character in the password
  112. for i in range(key_len):
  113. h.update(key)
  114.  
  115. temp_result = h.digest()
  116.  
  117. # Compute a P array of the bytes in temp repeated for the length of the key
  118. p_bytes = temp_result * (key_len // digest_size)
  119. p_bytes += temp_result[:(key_len % digest_size)]
  120.  
  121. alt_h = hashfunc()
  122.  
  123. # Add the salt 16 + arbitrary amount decided by first byte in alt digest
  124. for i in range(16 + byte2int(alt_result[0])):
  125. alt_h.update(salt.encode('utf-8'))
  126.  
  127. temp_result = alt_h.digest()
  128.  
  129. # Compute a S array of the bytes in temp_result repeated for the length
  130. # of the salt
  131. s_bytes = temp_result * (len(salt) // digest_size)
  132. s_bytes += temp_result[:(len(salt) % digest_size)]
  133.  
  134. # Do the actual iterations
  135. for i in range(rounds):
  136. h = hashfunc()
  137.  
  138. # Alternate adding either the P array or the alt digest
  139. if i & 1 != 0:
  140. h.update(p_bytes)
  141. else:
  142. h.update(alt_result)
  143.  
  144. # If the round is divisible by 3, add the S array
  145. if i % 3 != 0:
  146. h.update(s_bytes)
  147.  
  148. # If the round is divisible by 7, add the P array
  149. if i % 7 != 0:
  150. h.update(p_bytes)
  151.  
  152. # Alternate adding either the P array or the alt digest, opposite
  153. # of first step
  154. if i & 1 != 0:
  155. h.update(alt_result)
  156. else:
  157. h.update(p_bytes)
  158.  
  159. alt_result = h.digest()
  160.  
  161. # Compute the base64-ish representation of the hash
  162. ret = []
  163. if digest_size == 64:
  164. # SHA-512
  165. ret.append(b64_from_24bit(alt_result[0], alt_result[21], alt_result[42], 4))
  166. ret.append(b64_from_24bit(alt_result[22], alt_result[43], alt_result[1], 4))
  167. ret.append(b64_from_24bit(alt_result[44], alt_result[2], alt_result[23], 4))
  168. ret.append(b64_from_24bit(alt_result[3], alt_result[24], alt_result[45], 4))
  169. ret.append(b64_from_24bit(alt_result[25], alt_result[46], alt_result[4], 4))
  170. ret.append(b64_from_24bit(alt_result[47], alt_result[5], alt_result[26], 4))
  171. ret.append(b64_from_24bit(alt_result[6], alt_result[27], alt_result[48], 4))
  172. ret.append(b64_from_24bit(alt_result[28], alt_result[49], alt_result[7], 4))
  173. ret.append(b64_from_24bit(alt_result[50], alt_result[8], alt_result[29], 4))
  174. ret.append(b64_from_24bit(alt_result[9], alt_result[30], alt_result[51], 4))
  175. ret.append(b64_from_24bit(alt_result[31], alt_result[52], alt_result[10], 4))
  176. ret.append(b64_from_24bit(alt_result[53], alt_result[11], alt_result[32], 4))
  177. ret.append(b64_from_24bit(alt_result[12], alt_result[33], alt_result[54], 4))
  178. ret.append(b64_from_24bit(alt_result[34], alt_result[55], alt_result[13], 4))
  179. ret.append(b64_from_24bit(alt_result[56], alt_result[14], alt_result[35], 4))
  180. ret.append(b64_from_24bit(alt_result[15], alt_result[36], alt_result[57], 4))
  181. ret.append(b64_from_24bit(alt_result[37], alt_result[58], alt_result[16], 4))
  182. ret.append(b64_from_24bit(alt_result[59], alt_result[17], alt_result[38], 4))
  183. ret.append(b64_from_24bit(alt_result[18], alt_result[39], alt_result[60], 4))
  184. ret.append(b64_from_24bit(alt_result[40], alt_result[61], alt_result[19], 4))
  185. ret.append(b64_from_24bit(alt_result[62], alt_result[20], alt_result[41], 4))
  186. ret.append(b64_from_24bit(int2byte(0), int2byte(0), alt_result[63], 2))
  187. else:
  188. # SHA-256
  189. ret.append(b64_from_24bit(alt_result[0], alt_result[10], alt_result[20], 4))
  190. ret.append(b64_from_24bit(alt_result[21], alt_result[1], alt_result[11], 4))
  191. ret.append(b64_from_24bit(alt_result[12], alt_result[22], alt_result[2], 4))
  192. ret.append(b64_from_24bit(alt_result[3], alt_result[13], alt_result[23], 4))
  193. ret.append(b64_from_24bit(alt_result[24], alt_result[4], alt_result[14], 4))
  194. ret.append(b64_from_24bit(alt_result[15], alt_result[25], alt_result[5], 4))
  195. ret.append(b64_from_24bit(alt_result[6], alt_result[16], alt_result[26], 4))
  196. ret.append(b64_from_24bit(alt_result[27], alt_result[7], alt_result[17], 4))
  197. ret.append(b64_from_24bit(alt_result[18], alt_result[28], alt_result[8], 4))
  198. ret.append(b64_from_24bit(alt_result[9], alt_result[19], alt_result[29], 4))
  199. ret.append(b64_from_24bit(int2byte(0), alt_result[31], alt_result[30], 3))
  200.  
  201. algo = 6 if digest_size == 64 else 5
  202. if rounds == _ROUNDS_DEFAULT:
  203. return '${0}${1}${2}'.format(algo, salt, ''.join(ret))
  204. else:
  205. return '${0}$rounds={1}${2}${3}'.format(algo, rounds, salt, ''.join(ret))
  206.  
  207.  
  208. def byte2int(value):
  209. if _PY2:
  210. return ord(value)
  211. else:
  212. return value
  213.  
  214.  
  215. def int2byte(value):
  216. if _PY2:
  217. return chr(value)
  218. else:
  219. return value
  220.  
  221.  
  222. def extract_components_from_salt(salt):
  223. salt_match = _SALT_RE.match(salt)
  224. if salt_match:
  225. algo, rounds, salt = salt_match.groups(_ROUNDS_DEFAULT)
  226. algo = int(algo)
  227. rounds = int(rounds)
  228. else:
  229. algo = 6
  230. rounds = _ROUNDS_DEFAULT
  231. return _namedtuple('Salt', 'algo rounds salt')(algo, rounds, salt)
  232.  
  233.  
  234. def b64_from_24bit(b2, b1, b0, n):
  235. b2 = byte2int(b2)
  236. b1 = byte2int(b1)
  237. b0 = byte2int(b0)
  238. index = b2 << 16 | b1 << 8 | b0
  239. ret = []
  240. for i in range(n):
  241. ret.append(_BASE64_CHARACTERS[index & 0x3f])
  242. index >>= 6
  243. return ''.join(ret)
  244.  
  245.  
  246. def cli(argv=None):
  247. parser = argparse.ArgumentParser(description='Compute a password hash for '
  248. 'SHA256/SHA512 in crypt(3)-compatible format. Password will be prompted for.')
  249. parser.add_argument('-r', '--rounds', default=_ROUNDS_DEFAULT, type=int,
  250. help='How many rounds of hashing to perform. More rounds are slower, making'
  251. ' it harder to reverse a hash through brute force. Default: %(default)s')
  252. parser.add_argument('-a', '--algo', choices=('sha256', 'sha512'), default='sha512',
  253. help='Which algorithm to use. Default: %(default)s')
  254. parser.add_argument('-s', '--single-prompt', action='store_true',
  255. help="Don't ask to repeat the password")
  256.  
  257. args = parser.parse_args(argv)
  258.  
  259. if not 1000 < args.rounds < 999999999:
  260. # limits fetched from crypt(3) source
  261. sys.stderr.write('Rounds must be between 1000 and 999999999.\n')
  262. sys.exit(1)
  263.  
  264. if sys.stdin.isatty():
  265. if args.single_prompt:
  266. password = getpass.getpass('Enter password: ')
  267. else:
  268. password = double_prompt_for_plaintext_password()
  269. else:
  270. password = sys.stdin.readline().rstrip('\n')
  271. method = METHOD_SHA256 if args.algo == 'sha256' else METHOD_SHA512
  272. print(crypt(password, method, rounds=args.rounds), end='\n' if sys.stdout.isatty() else '')
  273.  
  274.  
  275. def double_prompt_for_plaintext_password():
  276. """Get the desired password from the user through a double prompt."""
  277. password = 1
  278. password_repeat = 2
  279. while password != password_repeat:
  280. password = getpass.getpass('Enter password: ')
  281. password_repeat = getpass.getpass('Repeat password: ')
  282. if password != password_repeat:
  283. sys.stderr.write('Passwords do not match, try again.\n')
  284. return password
  285.  
  286. print(crypt("MySecretPassword", "$5$LgsPuaeR"))
  287.  
Success #stdin #stdout 0.06s 11816KB
stdin
Standard input is empty
stdout
$5$LgsPuaeR$OCtm.3tpbS/wyOZAIy6dsVNP4x0GyohyGebkIz15e88