You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

513 rivejä
20 KiB

  1. """passlib.handlers.django- Django password hash support"""
  2. #=============================================================================
  3. # imports
  4. #=============================================================================
  5. # core
  6. from base64 import b64encode
  7. from binascii import hexlify
  8. from hashlib import md5, sha1, sha256
  9. import logging; log = logging.getLogger(__name__)
  10. # site
  11. # pkg
  12. from passlib.handlers.bcrypt import _wrapped_bcrypt
  13. from passlib.hash import argon2, bcrypt, pbkdf2_sha1, pbkdf2_sha256
  14. from passlib.utils import to_unicode, rng, getrandstr
  15. from passlib.utils.binary import BASE64_CHARS
  16. from passlib.utils.compat import str_to_uascii, uascii_to_str, unicode, u
  17. from passlib.crypto.digest import pbkdf2_hmac
  18. import passlib.utils.handlers as uh
  19. # local
  20. __all__ = [
  21. "django_salted_sha1",
  22. "django_salted_md5",
  23. "django_bcrypt",
  24. "django_pbkdf2_sha1",
  25. "django_pbkdf2_sha256",
  26. "django_argon2",
  27. "django_des_crypt",
  28. "django_disabled",
  29. ]
  30. #=============================================================================
  31. # lazy imports & constants
  32. #=============================================================================
  33. # imported by django_des_crypt._calc_checksum()
  34. des_crypt = None
  35. def _import_des_crypt():
  36. global des_crypt
  37. if des_crypt is None:
  38. from passlib.hash import des_crypt
  39. return des_crypt
  40. # django 1.4's salt charset
  41. SALT_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
  42. #=============================================================================
  43. # salted hashes
  44. #=============================================================================
  45. class DjangoSaltedHash(uh.HasSalt, uh.GenericHandler):
  46. """base class providing common code for django hashes"""
  47. # name, ident, checksum_size must be set by subclass.
  48. # ident must include "$" suffix.
  49. setting_kwds = ("salt", "salt_size")
  50. # NOTE: django 1.0-1.3 would accept empty salt strings.
  51. # django 1.4 won't, but this appears to be regression
  52. # (https://code.djangoproject.com/ticket/18144)
  53. # so presumably it will be fixed in a later release.
  54. default_salt_size = 12
  55. max_salt_size = None
  56. salt_chars = SALT_CHARS
  57. checksum_chars = uh.LOWER_HEX_CHARS
  58. @classmethod
  59. def from_string(cls, hash):
  60. salt, chk = uh.parse_mc2(hash, cls.ident, handler=cls)
  61. return cls(salt=salt, checksum=chk)
  62. def to_string(self):
  63. return uh.render_mc2(self.ident, self.salt, self.checksum)
  64. # NOTE: only used by PBKDF2
  65. class DjangoVariableHash(uh.HasRounds, DjangoSaltedHash):
  66. """base class providing common code for django hashes w/ variable rounds"""
  67. setting_kwds = DjangoSaltedHash.setting_kwds + ("rounds",)
  68. min_rounds = 1
  69. @classmethod
  70. def from_string(cls, hash):
  71. rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls)
  72. return cls(rounds=rounds, salt=salt, checksum=chk)
  73. def to_string(self):
  74. return uh.render_mc3(self.ident, self.rounds, self.salt, self.checksum)
  75. class django_salted_sha1(DjangoSaltedHash):
  76. """This class implements Django's Salted SHA1 hash, and follows the :ref:`password-hash-api`.
  77. It supports a variable-length salt, and uses a single round of SHA1.
  78. The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
  79. :type salt: str
  80. :param salt:
  81. Optional salt string.
  82. If not specified, a 12 character one will be autogenerated (this is recommended).
  83. If specified, may be any series of characters drawn from the regexp range ``[0-9a-zA-Z]``.
  84. :type salt_size: int
  85. :param salt_size:
  86. Optional number of characters to use when autogenerating new salts.
  87. Defaults to 12, but can be any positive value.
  88. This should be compatible with Django 1.4's :class:`!SHA1PasswordHasher` class.
  89. .. versionchanged: 1.6
  90. This class now generates 12-character salts instead of 5,
  91. and generated salts uses the character range ``[0-9a-zA-Z]`` instead of
  92. the ``[0-9a-f]``. This is to be compatible with how Django >= 1.4
  93. generates these hashes; but hashes generated in this manner will still be
  94. correctly interpreted by earlier versions of Django.
  95. """
  96. name = "django_salted_sha1"
  97. django_name = "sha1"
  98. ident = u("sha1$")
  99. checksum_size = 40
  100. def _calc_checksum(self, secret):
  101. if isinstance(secret, unicode):
  102. secret = secret.encode("utf-8")
  103. return str_to_uascii(sha1(self.salt.encode("ascii") + secret).hexdigest())
  104. class django_salted_md5(DjangoSaltedHash):
  105. """This class implements Django's Salted MD5 hash, and follows the :ref:`password-hash-api`.
  106. It supports a variable-length salt, and uses a single round of MD5.
  107. The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
  108. :type salt: str
  109. :param salt:
  110. Optional salt string.
  111. If not specified, a 12 character one will be autogenerated (this is recommended).
  112. If specified, may be any series of characters drawn from the regexp range ``[0-9a-zA-Z]``.
  113. :type salt_size: int
  114. :param salt_size:
  115. Optional number of characters to use when autogenerating new salts.
  116. Defaults to 12, but can be any positive value.
  117. This should be compatible with the hashes generated by
  118. Django 1.4's :class:`!MD5PasswordHasher` class.
  119. .. versionchanged: 1.6
  120. This class now generates 12-character salts instead of 5,
  121. and generated salts uses the character range ``[0-9a-zA-Z]`` instead of
  122. the ``[0-9a-f]``. This is to be compatible with how Django >= 1.4
  123. generates these hashes; but hashes generated in this manner will still be
  124. correctly interpreted by earlier versions of Django.
  125. """
  126. name = "django_salted_md5"
  127. django_name = "md5"
  128. ident = u("md5$")
  129. checksum_size = 32
  130. def _calc_checksum(self, secret):
  131. if isinstance(secret, unicode):
  132. secret = secret.encode("utf-8")
  133. return str_to_uascii(md5(self.salt.encode("ascii") + secret).hexdigest())
  134. #=============================================================================
  135. # BCrypt
  136. #=============================================================================
  137. django_bcrypt = uh.PrefixWrapper("django_bcrypt", bcrypt,
  138. prefix=u('bcrypt$'), ident=u("bcrypt$"),
  139. # NOTE: this docstring is duplicated in the docs, since sphinx
  140. # seems to be having trouble reading it via autodata::
  141. doc="""This class implements Django 1.4's BCrypt wrapper, and follows the :ref:`password-hash-api`.
  142. This is identical to :class:`!bcrypt` itself, but with
  143. the Django-specific prefix ``"bcrypt$"`` prepended.
  144. See :doc:`/lib/passlib.hash.bcrypt` for more details,
  145. the usage and behavior is identical.
  146. This should be compatible with the hashes generated by
  147. Django 1.4's :class:`!BCryptPasswordHasher` class.
  148. .. versionadded:: 1.6
  149. """)
  150. django_bcrypt.django_name = "bcrypt"
  151. django_bcrypt._using_clone_attrs += ("django_name",)
  152. #=============================================================================
  153. # BCRYPT + SHA256
  154. #=============================================================================
  155. class django_bcrypt_sha256(_wrapped_bcrypt):
  156. """This class implements Django 1.6's Bcrypt+SHA256 hash, and follows the :ref:`password-hash-api`.
  157. It supports a variable-length salt, and a variable number of rounds.
  158. While the algorithm and format is somewhat different,
  159. the api and options for this hash are identical to :class:`!bcrypt` itself,
  160. see :doc:`bcrypt </lib/passlib.hash.bcrypt>` for more details.
  161. .. versionadded:: 1.6.2
  162. """
  163. name = "django_bcrypt_sha256"
  164. django_name = "bcrypt_sha256"
  165. _digest = sha256
  166. # sample hash:
  167. # bcrypt_sha256$$2a$06$/3OeRpbOf8/l6nPPRdZPp.nRiyYqPobEZGdNRBWihQhiFDh1ws1tu
  168. # XXX: we can't use .ident attr due to bcrypt code using it.
  169. # working around that via django_prefix
  170. django_prefix = u('bcrypt_sha256$')
  171. @classmethod
  172. def identify(cls, hash):
  173. hash = uh.to_unicode_for_identify(hash)
  174. if not hash:
  175. return False
  176. return hash.startswith(cls.django_prefix)
  177. @classmethod
  178. def from_string(cls, hash):
  179. hash = to_unicode(hash, "ascii", "hash")
  180. if not hash.startswith(cls.django_prefix):
  181. raise uh.exc.InvalidHashError(cls)
  182. bhash = hash[len(cls.django_prefix):]
  183. if not bhash.startswith("$2"):
  184. raise uh.exc.MalformedHashError(cls)
  185. return super(django_bcrypt_sha256, cls).from_string(bhash)
  186. def to_string(self):
  187. bhash = super(django_bcrypt_sha256, self).to_string()
  188. return uascii_to_str(self.django_prefix) + bhash
  189. def _calc_checksum(self, secret):
  190. if isinstance(secret, unicode):
  191. secret = secret.encode("utf-8")
  192. secret = hexlify(self._digest(secret).digest())
  193. return super(django_bcrypt_sha256, self)._calc_checksum(secret)
  194. #=============================================================================
  195. # PBKDF2 variants
  196. #=============================================================================
  197. class django_pbkdf2_sha256(DjangoVariableHash):
  198. """This class implements Django's PBKDF2-HMAC-SHA256 hash, and follows the :ref:`password-hash-api`.
  199. It supports a variable-length salt, and a variable number of rounds.
  200. The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
  201. :type salt: str
  202. :param salt:
  203. Optional salt string.
  204. If not specified, a 12 character one will be autogenerated (this is recommended).
  205. If specified, may be any series of characters drawn from the regexp range ``[0-9a-zA-Z]``.
  206. :type salt_size: int
  207. :param salt_size:
  208. Optional number of characters to use when autogenerating new salts.
  209. Defaults to 12, but can be any positive value.
  210. :type rounds: int
  211. :param rounds:
  212. Optional number of rounds to use.
  213. Defaults to 29000, but must be within ``range(1,1<<32)``.
  214. :type relaxed: bool
  215. :param relaxed:
  216. By default, providing an invalid value for one of the other
  217. keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
  218. and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
  219. will be issued instead. Correctable errors include ``rounds``
  220. that are too small or too large, and ``salt`` strings that are too long.
  221. This should be compatible with the hashes generated by
  222. Django 1.4's :class:`!PBKDF2PasswordHasher` class.
  223. .. versionadded:: 1.6
  224. """
  225. name = "django_pbkdf2_sha256"
  226. django_name = "pbkdf2_sha256"
  227. ident = u('pbkdf2_sha256$')
  228. min_salt_size = 1
  229. max_rounds = 0xffffffff # setting at 32-bit limit for now
  230. checksum_chars = uh.PADDED_BASE64_CHARS
  231. checksum_size = 44 # 32 bytes -> base64
  232. default_rounds = pbkdf2_sha256.default_rounds # NOTE: django 1.6 uses 12000
  233. _digest = "sha256"
  234. def _calc_checksum(self, secret):
  235. # NOTE: secret & salt will be encoded using UTF-8 by pbkdf2_hmac()
  236. hash = pbkdf2_hmac(self._digest, secret, self.salt, self.rounds)
  237. return b64encode(hash).rstrip().decode("ascii")
  238. class django_pbkdf2_sha1(django_pbkdf2_sha256):
  239. """This class implements Django's PBKDF2-HMAC-SHA1 hash, and follows the :ref:`password-hash-api`.
  240. It supports a variable-length salt, and a variable number of rounds.
  241. The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
  242. :type salt: str
  243. :param salt:
  244. Optional salt string.
  245. If not specified, a 12 character one will be autogenerated (this is recommended).
  246. If specified, may be any series of characters drawn from the regexp range ``[0-9a-zA-Z]``.
  247. :type salt_size: int
  248. :param salt_size:
  249. Optional number of characters to use when autogenerating new salts.
  250. Defaults to 12, but can be any positive value.
  251. :type rounds: int
  252. :param rounds:
  253. Optional number of rounds to use.
  254. Defaults to 131000, but must be within ``range(1,1<<32)``.
  255. :type relaxed: bool
  256. :param relaxed:
  257. By default, providing an invalid value for one of the other
  258. keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
  259. and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
  260. will be issued instead. Correctable errors include ``rounds``
  261. that are too small or too large, and ``salt`` strings that are too long.
  262. This should be compatible with the hashes generated by
  263. Django 1.4's :class:`!PBKDF2SHA1PasswordHasher` class.
  264. .. versionadded:: 1.6
  265. """
  266. name = "django_pbkdf2_sha1"
  267. django_name = "pbkdf2_sha1"
  268. ident = u('pbkdf2_sha1$')
  269. checksum_size = 28 # 20 bytes -> base64
  270. default_rounds = pbkdf2_sha1.default_rounds # NOTE: django 1.6 uses 12000
  271. _digest = "sha1"
  272. #=============================================================================
  273. # Argon2
  274. #=============================================================================
  275. # NOTE: as of 2019-11-11, Django's Argon2PasswordHasher only supports Type I;
  276. # so limiting this to ensure that as well.
  277. django_argon2 = uh.PrefixWrapper(
  278. name="django_argon2",
  279. wrapped=argon2.using(type="I"),
  280. prefix=u('argon2'),
  281. ident=u('argon2$argon2i$'),
  282. # NOTE: this docstring is duplicated in the docs, since sphinx
  283. # seems to be having trouble reading it via autodata::
  284. doc="""This class implements Django 1.10's Argon2 wrapper, and follows the :ref:`password-hash-api`.
  285. This is identical to :class:`!argon2` itself, but with
  286. the Django-specific prefix ``"argon2$"`` prepended.
  287. See :doc:`argon2 </lib/passlib.hash.argon2>` for more details,
  288. the usage and behavior is identical.
  289. This should be compatible with the hashes generated by
  290. Django 1.10's :class:`!Argon2PasswordHasher` class.
  291. .. versionadded:: 1.7
  292. """)
  293. django_argon2.django_name = "argon2"
  294. django_argon2._using_clone_attrs += ("django_name",)
  295. #=============================================================================
  296. # DES
  297. #=============================================================================
  298. class django_des_crypt(uh.TruncateMixin, uh.HasSalt, uh.GenericHandler):
  299. """This class implements Django's :class:`des_crypt` wrapper, and follows the :ref:`password-hash-api`.
  300. It supports a fixed-length salt.
  301. The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords:
  302. :type salt: str
  303. :param salt:
  304. Optional salt string.
  305. If not specified, one will be autogenerated (this is recommended).
  306. If specified, it must be 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
  307. :param bool truncate_error:
  308. By default, django_des_crypt will silently truncate passwords larger than 8 bytes.
  309. Setting ``truncate_error=True`` will cause :meth:`~passlib.ifc.PasswordHash.hash`
  310. to raise a :exc:`~passlib.exc.PasswordTruncateError` instead.
  311. .. versionadded:: 1.7
  312. This should be compatible with the hashes generated by
  313. Django 1.4's :class:`!CryptPasswordHasher` class.
  314. Note that Django only supports this hash on Unix systems
  315. (though :class:`!django_des_crypt` is available cross-platform
  316. under Passlib).
  317. .. versionchanged:: 1.6
  318. This class will now accept hashes with empty salt strings,
  319. since Django 1.4 generates them this way.
  320. """
  321. name = "django_des_crypt"
  322. django_name = "crypt"
  323. setting_kwds = ("salt", "salt_size", "truncate_error")
  324. ident = u("crypt$")
  325. checksum_chars = salt_chars = uh.HASH64_CHARS
  326. checksum_size = 11
  327. min_salt_size = default_salt_size = 2
  328. truncate_size = 8
  329. # NOTE: regarding duplicate salt field:
  330. #
  331. # django 1.0 had a "crypt$<salt1>$<salt2><digest>" hash format,
  332. # used [a-z0-9] to generate a 5 char salt, stored it in salt1,
  333. # duplicated the first two chars of salt1 as salt2.
  334. # it would throw an error if salt1 was empty.
  335. #
  336. # django 1.4 started generating 2 char salt using the full alphabet,
  337. # left salt1 empty, and only paid attention to salt2.
  338. #
  339. # in order to be compatible with django 1.0, the hashes generated
  340. # by this function will always include salt1, unless the following
  341. # class-level field is disabled (mainly used for testing)
  342. use_duplicate_salt = True
  343. @classmethod
  344. def from_string(cls, hash):
  345. salt, chk = uh.parse_mc2(hash, cls.ident, handler=cls)
  346. if chk:
  347. # chk should be full des_crypt hash
  348. if not salt:
  349. # django 1.4 always uses empty salt field,
  350. # so extract salt from des_crypt hash <chk>
  351. salt = chk[:2]
  352. elif salt[:2] != chk[:2]:
  353. # django 1.0 stored 5 chars in salt field, and duplicated
  354. # the first two chars in <chk>. we keep the full salt,
  355. # but make sure the first two chars match as sanity check.
  356. raise uh.exc.MalformedHashError(cls,
  357. "first two digits of salt and checksum must match")
  358. # in all cases, strip salt chars from <chk>
  359. chk = chk[2:]
  360. return cls(salt=salt, checksum=chk)
  361. def to_string(self):
  362. salt = self.salt
  363. chk = salt[:2] + self.checksum
  364. if self.use_duplicate_salt:
  365. # filling in salt field, so that we're compatible with django 1.0
  366. return uh.render_mc2(self.ident, salt, chk)
  367. else:
  368. # django 1.4+ style hash
  369. return uh.render_mc2(self.ident, "", chk)
  370. def _calc_checksum(self, secret):
  371. # NOTE: we lazily import des_crypt,
  372. # since most django deploys won't use django_des_crypt
  373. global des_crypt
  374. if des_crypt is None:
  375. _import_des_crypt()
  376. # check for truncation (during .hash() calls only)
  377. if self.use_defaults:
  378. self._check_truncate_policy(secret)
  379. return des_crypt(salt=self.salt[:2])._calc_checksum(secret)
  380. class django_disabled(uh.ifc.DisabledHash, uh.StaticHandler):
  381. """This class provides disabled password behavior for Django, and follows the :ref:`password-hash-api`.
  382. This class does not implement a hash, but instead
  383. claims the special hash string ``"!"`` which Django uses
  384. to indicate an account's password has been disabled.
  385. * newly encrypted passwords will hash to ``"!"``.
  386. * it rejects all passwords.
  387. .. note::
  388. Django 1.6 prepends a randomly generated 40-char alphanumeric string
  389. to each unusuable password. This class recognizes such strings,
  390. but for backwards compatibility, still returns ``"!"``.
  391. See `<https://code.djangoproject.com/ticket/20079>`_ for why
  392. Django appends an alphanumeric string.
  393. .. versionchanged:: 1.6.2 added Django 1.6 support
  394. .. versionchanged:: 1.7 started appending an alphanumeric string.
  395. """
  396. name = "django_disabled"
  397. _hash_prefix = u("!")
  398. suffix_length = 40
  399. # XXX: move this to StaticHandler, or wherever _hash_prefix is being used?
  400. @classmethod
  401. def identify(cls, hash):
  402. hash = uh.to_unicode_for_identify(hash)
  403. return hash.startswith(cls._hash_prefix)
  404. def _calc_checksum(self, secret):
  405. # generate random suffix to match django's behavior
  406. return getrandstr(rng, BASE64_CHARS[:-2], self.suffix_length)
  407. @classmethod
  408. def verify(cls, secret, hash):
  409. uh.validate_secret(secret)
  410. if not cls.identify(hash):
  411. raise uh.exc.InvalidHashError(cls)
  412. return False
  413. #=============================================================================
  414. # eof
  415. #=============================================================================