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