hashers.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. from __future__ import unicode_literals
  2. import base64
  3. import binascii
  4. from collections import OrderedDict
  5. import hashlib
  6. import importlib
  7. from django.dispatch import receiver
  8. from django.conf import settings
  9. from django.test.signals import setting_changed
  10. from django.utils.encoding import force_bytes, force_str, force_text
  11. from django.core.exceptions import ImproperlyConfigured
  12. from django.utils.crypto import (
  13. pbkdf2, constant_time_compare, get_random_string)
  14. from django.utils.module_loading import import_string
  15. from django.utils.translation import ugettext_noop as _
  16. UNUSABLE_PASSWORD_PREFIX = '!' # This will never be a valid encoded hash
  17. UNUSABLE_PASSWORD_SUFFIX_LENGTH = 40 # number of random chars to add after UNUSABLE_PASSWORD_PREFIX
  18. HASHERS = None # lazily loaded from PASSWORD_HASHERS
  19. PREFERRED_HASHER = None # defaults to first item in PASSWORD_HASHERS
  20. @receiver(setting_changed)
  21. def reset_hashers(**kwargs):
  22. if kwargs['setting'] == 'PASSWORD_HASHERS':
  23. global HASHERS, PREFERRED_HASHER
  24. HASHERS = None
  25. PREFERRED_HASHER = None
  26. def is_password_usable(encoded):
  27. if encoded is None or encoded.startswith(UNUSABLE_PASSWORD_PREFIX):
  28. return False
  29. try:
  30. identify_hasher(encoded)
  31. except ValueError:
  32. return False
  33. return True
  34. def check_password(password, encoded, setter=None, preferred='default'):
  35. """
  36. Returns a boolean of whether the raw password matches the three
  37. part encoded digest.
  38. If setter is specified, it'll be called when you need to
  39. regenerate the password.
  40. """
  41. if password is None or not is_password_usable(encoded):
  42. return False
  43. preferred = get_hasher(preferred)
  44. hasher = identify_hasher(encoded)
  45. must_update = hasher.algorithm != preferred.algorithm
  46. if not must_update:
  47. must_update = preferred.must_update(encoded)
  48. is_correct = hasher.verify(password, encoded)
  49. if setter and is_correct and must_update:
  50. setter(password)
  51. return is_correct
  52. def make_password(password, salt=None, hasher='default'):
  53. """
  54. Turn a plain-text password into a hash for database storage
  55. Same as encode() but generates a new random salt.
  56. If password is None then a concatenation of
  57. UNUSABLE_PASSWORD_PREFIX and a random string will be returned
  58. which disallows logins. Additional random string reduces chances
  59. of gaining access to staff or superuser accounts.
  60. See ticket #20079 for more info.
  61. """
  62. if password is None:
  63. return UNUSABLE_PASSWORD_PREFIX + get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH)
  64. hasher = get_hasher(hasher)
  65. if not salt:
  66. salt = hasher.salt()
  67. return hasher.encode(password, salt)
  68. def load_hashers(password_hashers=None):
  69. global HASHERS
  70. global PREFERRED_HASHER
  71. hashers = []
  72. if not password_hashers:
  73. password_hashers = settings.PASSWORD_HASHERS
  74. for backend in password_hashers:
  75. hasher = import_string(backend)()
  76. if not getattr(hasher, 'algorithm'):
  77. raise ImproperlyConfigured("hasher doesn't specify an "
  78. "algorithm name: %s" % backend)
  79. hashers.append(hasher)
  80. HASHERS = dict((hasher.algorithm, hasher) for hasher in hashers)
  81. PREFERRED_HASHER = hashers[0]
  82. def get_hasher(algorithm='default'):
  83. """
  84. Returns an instance of a loaded password hasher.
  85. If algorithm is 'default', the default hasher will be returned.
  86. This function will also lazy import hashers specified in your
  87. settings file if needed.
  88. """
  89. if hasattr(algorithm, 'algorithm'):
  90. return algorithm
  91. elif algorithm == 'default':
  92. if PREFERRED_HASHER is None:
  93. load_hashers()
  94. return PREFERRED_HASHER
  95. else:
  96. if HASHERS is None:
  97. load_hashers()
  98. if algorithm not in HASHERS:
  99. raise ValueError("Unknown password hashing algorithm '%s'. "
  100. "Did you specify it in the PASSWORD_HASHERS "
  101. "setting?" % algorithm)
  102. return HASHERS[algorithm]
  103. def identify_hasher(encoded):
  104. """
  105. Returns an instance of a loaded password hasher.
  106. Identifies hasher algorithm by examining encoded hash, and calls
  107. get_hasher() to return hasher. Raises ValueError if
  108. algorithm cannot be identified, or if hasher is not loaded.
  109. """
  110. # Ancient versions of Django created plain MD5 passwords and accepted
  111. # MD5 passwords with an empty salt.
  112. if ((len(encoded) == 32 and '$' not in encoded) or
  113. (len(encoded) == 37 and encoded.startswith('md5$$'))):
  114. algorithm = 'unsalted_md5'
  115. # Ancient versions of Django accepted SHA1 passwords with an empty salt.
  116. elif len(encoded) == 46 and encoded.startswith('sha1$$'):
  117. algorithm = 'unsalted_sha1'
  118. else:
  119. algorithm = encoded.split('$', 1)[0]
  120. return get_hasher(algorithm)
  121. def mask_hash(hash, show=6, char="*"):
  122. """
  123. Returns the given hash, with only the first ``show`` number shown. The
  124. rest are masked with ``char`` for security reasons.
  125. """
  126. masked = hash[:show]
  127. masked += char * len(hash[show:])
  128. return masked
  129. class BasePasswordHasher(object):
  130. """
  131. Abstract base class for password hashers
  132. When creating your own hasher, you need to override algorithm,
  133. verify(), encode() and safe_summary().
  134. PasswordHasher objects are immutable.
  135. """
  136. algorithm = None
  137. library = None
  138. def _load_library(self):
  139. if self.library is not None:
  140. if isinstance(self.library, (tuple, list)):
  141. name, mod_path = self.library
  142. else:
  143. mod_path = self.library
  144. try:
  145. module = importlib.import_module(mod_path)
  146. except ImportError as e:
  147. raise ValueError("Couldn't load %r algorithm library: %s" %
  148. (self.__class__.__name__, e))
  149. return module
  150. raise ValueError("Hasher %r doesn't specify a library attribute" %
  151. self.__class__.__name__)
  152. def salt(self):
  153. """
  154. Generates a cryptographically secure nonce salt in ASCII
  155. """
  156. return get_random_string()
  157. def verify(self, password, encoded):
  158. """
  159. Checks if the given password is correct
  160. """
  161. raise NotImplementedError('subclasses of BasePasswordHasher must provide a verify() method')
  162. def encode(self, password, salt):
  163. """
  164. Creates an encoded database value
  165. The result is normally formatted as "algorithm$salt$hash" and
  166. must be fewer than 128 characters.
  167. """
  168. raise NotImplementedError('subclasses of BasePasswordHasher must provide an encode() method')
  169. def safe_summary(self, encoded):
  170. """
  171. Returns a summary of safe values
  172. The result is a dictionary and will be used where the password field
  173. must be displayed to construct a safe representation of the password.
  174. """
  175. raise NotImplementedError('subclasses of BasePasswordHasher must provide a safe_summary() method')
  176. def must_update(self, encoded):
  177. return False
  178. class PBKDF2PasswordHasher(BasePasswordHasher):
  179. """
  180. Secure password hashing using the PBKDF2 algorithm (recommended)
  181. Configured to use PBKDF2 + HMAC + SHA256 with 12000 iterations.
  182. The result is a 64 byte binary string. Iterations may be changed
  183. safely but you must rename the algorithm if you change SHA256.
  184. """
  185. algorithm = "pbkdf2_sha256"
  186. iterations = 12000
  187. digest = hashlib.sha256
  188. def encode(self, password, salt, iterations=None):
  189. assert password is not None
  190. assert salt and '$' not in salt
  191. if not iterations:
  192. iterations = self.iterations
  193. hash = pbkdf2(password, salt, iterations, digest=self.digest)
  194. hash = base64.b64encode(hash).decode('ascii').strip()
  195. return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
  196. def verify(self, password, encoded):
  197. algorithm, iterations, salt, hash = encoded.split('$', 3)
  198. assert algorithm == self.algorithm
  199. encoded_2 = self.encode(password, salt, int(iterations))
  200. return constant_time_compare(encoded, encoded_2)
  201. def safe_summary(self, encoded):
  202. algorithm, iterations, salt, hash = encoded.split('$', 3)
  203. assert algorithm == self.algorithm
  204. return OrderedDict([
  205. (_('algorithm'), algorithm),
  206. (_('iterations'), iterations),
  207. (_('salt'), mask_hash(salt)),
  208. (_('hash'), mask_hash(hash)),
  209. ])
  210. def must_update(self, encoded):
  211. algorithm, iterations, salt, hash = encoded.split('$', 3)
  212. return int(iterations) != self.iterations
  213. class PBKDF2SHA1PasswordHasher(PBKDF2PasswordHasher):
  214. """
  215. Alternate PBKDF2 hasher which uses SHA1, the default PRF
  216. recommended by PKCS #5. This is compatible with other
  217. implementations of PBKDF2, such as openssl's
  218. PKCS5_PBKDF2_HMAC_SHA1().
  219. """
  220. algorithm = "pbkdf2_sha1"
  221. digest = hashlib.sha1
  222. class BCryptSHA256PasswordHasher(BasePasswordHasher):
  223. """
  224. Secure password hashing using the bcrypt algorithm (recommended)
  225. This is considered by many to be the most secure algorithm but you
  226. must first install the bcrypt library. Please be warned that
  227. this library depends on native C code and might cause portability
  228. issues.
  229. """
  230. algorithm = "bcrypt_sha256"
  231. digest = hashlib.sha256
  232. library = ("bcrypt", "bcrypt")
  233. rounds = 12
  234. def salt(self):
  235. bcrypt = self._load_library()
  236. return bcrypt.gensalt(self.rounds)
  237. def encode(self, password, salt):
  238. bcrypt = self._load_library()
  239. # Need to reevaluate the force_bytes call once bcrypt is supported on
  240. # Python 3
  241. # Hash the password prior to using bcrypt to prevent password truncation
  242. # See: https://code.djangoproject.com/ticket/20138
  243. if self.digest is not None:
  244. # We use binascii.hexlify here because Python3 decided that a hex encoded
  245. # bytestring is somehow a unicode.
  246. password = binascii.hexlify(self.digest(force_bytes(password)).digest())
  247. else:
  248. password = force_bytes(password)
  249. data = bcrypt.hashpw(password, salt)
  250. return "%s$%s" % (self.algorithm, force_text(data))
  251. def verify(self, password, encoded):
  252. algorithm, data = encoded.split('$', 1)
  253. assert algorithm == self.algorithm
  254. bcrypt = self._load_library()
  255. # Hash the password prior to using bcrypt to prevent password truncation
  256. # See: https://code.djangoproject.com/ticket/20138
  257. if self.digest is not None:
  258. # We use binascii.hexlify here because Python3 decided that a hex encoded
  259. # bytestring is somehow a unicode.
  260. password = binascii.hexlify(self.digest(force_bytes(password)).digest())
  261. else:
  262. password = force_bytes(password)
  263. # Ensure that our data is a bytestring
  264. data = force_bytes(data)
  265. # force_bytes() necessary for py-bcrypt compatibility
  266. hashpw = force_bytes(bcrypt.hashpw(password, data))
  267. return constant_time_compare(data, hashpw)
  268. def safe_summary(self, encoded):
  269. algorithm, empty, algostr, work_factor, data = encoded.split('$', 4)
  270. assert algorithm == self.algorithm
  271. salt, checksum = data[:22], data[22:]
  272. return OrderedDict([
  273. (_('algorithm'), algorithm),
  274. (_('work factor'), work_factor),
  275. (_('salt'), mask_hash(salt)),
  276. (_('checksum'), mask_hash(checksum)),
  277. ])
  278. class BCryptPasswordHasher(BCryptSHA256PasswordHasher):
  279. """
  280. Secure password hashing using the bcrypt algorithm
  281. This is considered by many to be the most secure algorithm but you
  282. must first install the bcrypt library. Please be warned that
  283. this library depends on native C code and might cause portability
  284. issues.
  285. This hasher does not first hash the password which means it is subject to
  286. the 72 character bcrypt password truncation, most use cases should prefer
  287. the BCryptSha512PasswordHasher.
  288. See: https://code.djangoproject.com/ticket/20138
  289. """
  290. algorithm = "bcrypt"
  291. digest = None
  292. class SHA1PasswordHasher(BasePasswordHasher):
  293. """
  294. The SHA1 password hashing algorithm (not recommended)
  295. """
  296. algorithm = "sha1"
  297. def encode(self, password, salt):
  298. assert password is not None
  299. assert salt and '$' not in salt
  300. hash = hashlib.sha1(force_bytes(salt + password)).hexdigest()
  301. return "%s$%s$%s" % (self.algorithm, salt, hash)
  302. def verify(self, password, encoded):
  303. algorithm, salt, hash = encoded.split('$', 2)
  304. assert algorithm == self.algorithm
  305. encoded_2 = self.encode(password, salt)
  306. return constant_time_compare(encoded, encoded_2)
  307. def safe_summary(self, encoded):
  308. algorithm, salt, hash = encoded.split('$', 2)
  309. assert algorithm == self.algorithm
  310. return OrderedDict([
  311. (_('algorithm'), algorithm),
  312. (_('salt'), mask_hash(salt, show=2)),
  313. (_('hash'), mask_hash(hash)),
  314. ])
  315. class MD5PasswordHasher(BasePasswordHasher):
  316. """
  317. The Salted MD5 password hashing algorithm (not recommended)
  318. """
  319. algorithm = "md5"
  320. def encode(self, password, salt):
  321. assert password is not None
  322. assert salt and '$' not in salt
  323. hash = hashlib.md5(force_bytes(salt + password)).hexdigest()
  324. return "%s$%s$%s" % (self.algorithm, salt, hash)
  325. def verify(self, password, encoded):
  326. algorithm, salt, hash = encoded.split('$', 2)
  327. assert algorithm == self.algorithm
  328. encoded_2 = self.encode(password, salt)
  329. return constant_time_compare(encoded, encoded_2)
  330. def safe_summary(self, encoded):
  331. algorithm, salt, hash = encoded.split('$', 2)
  332. assert algorithm == self.algorithm
  333. return OrderedDict([
  334. (_('algorithm'), algorithm),
  335. (_('salt'), mask_hash(salt, show=2)),
  336. (_('hash'), mask_hash(hash)),
  337. ])
  338. class UnsaltedSHA1PasswordHasher(BasePasswordHasher):
  339. """
  340. Very insecure algorithm that you should *never* use; stores SHA1 hashes
  341. with an empty salt.
  342. This class is implemented because Django used to accept such password
  343. hashes. Some older Django installs still have these values lingering
  344. around so we need to handle and upgrade them properly.
  345. """
  346. algorithm = "unsalted_sha1"
  347. def salt(self):
  348. return ''
  349. def encode(self, password, salt):
  350. assert salt == ''
  351. hash = hashlib.sha1(force_bytes(password)).hexdigest()
  352. return 'sha1$$%s' % hash
  353. def verify(self, password, encoded):
  354. encoded_2 = self.encode(password, '')
  355. return constant_time_compare(encoded, encoded_2)
  356. def safe_summary(self, encoded):
  357. assert encoded.startswith('sha1$$')
  358. hash = encoded[6:]
  359. return OrderedDict([
  360. (_('algorithm'), self.algorithm),
  361. (_('hash'), mask_hash(hash)),
  362. ])
  363. class UnsaltedMD5PasswordHasher(BasePasswordHasher):
  364. """
  365. Incredibly insecure algorithm that you should *never* use; stores unsalted
  366. MD5 hashes without the algorithm prefix, also accepts MD5 hashes with an
  367. empty salt.
  368. This class is implemented because Django used to store passwords this way
  369. and to accept such password hashes. Some older Django installs still have
  370. these values lingering around so we need to handle and upgrade them
  371. properly.
  372. """
  373. algorithm = "unsalted_md5"
  374. def salt(self):
  375. return ''
  376. def encode(self, password, salt):
  377. assert salt == ''
  378. return hashlib.md5(force_bytes(password)).hexdigest()
  379. def verify(self, password, encoded):
  380. if len(encoded) == 37 and encoded.startswith('md5$$'):
  381. encoded = encoded[5:]
  382. encoded_2 = self.encode(password, '')
  383. return constant_time_compare(encoded, encoded_2)
  384. def safe_summary(self, encoded):
  385. return OrderedDict([
  386. (_('algorithm'), self.algorithm),
  387. (_('hash'), mask_hash(encoded, show=3)),
  388. ])
  389. class CryptPasswordHasher(BasePasswordHasher):
  390. """
  391. Password hashing using UNIX crypt (not recommended)
  392. The crypt module is not supported on all platforms.
  393. """
  394. algorithm = "crypt"
  395. library = "crypt"
  396. def salt(self):
  397. return get_random_string(2)
  398. def encode(self, password, salt):
  399. crypt = self._load_library()
  400. assert len(salt) == 2
  401. data = crypt.crypt(force_str(password), salt)
  402. # we don't need to store the salt, but Django used to do this
  403. return "%s$%s$%s" % (self.algorithm, '', data)
  404. def verify(self, password, encoded):
  405. crypt = self._load_library()
  406. algorithm, salt, data = encoded.split('$', 2)
  407. assert algorithm == self.algorithm
  408. return constant_time_compare(data, crypt.crypt(force_str(password), data))
  409. def safe_summary(self, encoded):
  410. algorithm, salt, data = encoded.split('$', 2)
  411. assert algorithm == self.algorithm
  412. return OrderedDict([
  413. (_('algorithm'), algorithm),
  414. (_('salt'), salt),
  415. (_('hash'), mask_hash(data, show=3)),
  416. ])