| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 |
- """passlib.handlers.pbkdf - PBKDF2 based hashes"""
- #=============================================================================
- # imports
- #=============================================================================
- # core
- from binascii import hexlify, unhexlify
- from base64 import b64encode, b64decode
- import logging; log = logging.getLogger(__name__)
- # site
- # pkg
- from passlib.utils import to_unicode
- from passlib.utils.binary import ab64_decode, ab64_encode
- from passlib.utils.compat import str_to_bascii, u, uascii_to_str, unicode
- from passlib.crypto.digest import pbkdf2_hmac
- import passlib.utils.handlers as uh
- # local
- __all__ = [
- "pbkdf2_sha1",
- "pbkdf2_sha256",
- "pbkdf2_sha512",
- "cta_pbkdf2_sha1",
- "dlitz_pbkdf2_sha1",
- "grub_pbkdf2_sha512",
- ]
- #=============================================================================
- #
- #=============================================================================
- class Pbkdf2DigestHandler(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
- """base class for various pbkdf2_{digest} algorithms"""
- #===================================================================
- # class attrs
- #===================================================================
- #--GenericHandler--
- setting_kwds = ("salt", "salt_size", "rounds")
- checksum_chars = uh.HASH64_CHARS
- #--HasSalt--
- default_salt_size = 16
- max_salt_size = 1024
- #--HasRounds--
- default_rounds = None # set by subclass
- min_rounds = 1
- max_rounds = 0xffffffff # setting at 32-bit limit for now
- rounds_cost = "linear"
- #--this class--
- _digest = None # name of subclass-specified hash
- # NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide sanity check.
- # the underlying pbkdf2 specifies no bounds for either.
- # NOTE: defaults chosen to be at least as large as pbkdf2 rfc recommends...
- # >8 bytes of entropy in salt, >1000 rounds
- # increased due to time since rfc established
- #===================================================================
- # methods
- #===================================================================
- @classmethod
- def from_string(cls, hash):
- rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls)
- salt = ab64_decode(salt.encode("ascii"))
- if chk:
- chk = ab64_decode(chk.encode("ascii"))
- return cls(rounds=rounds, salt=salt, checksum=chk)
- def to_string(self):
- salt = ab64_encode(self.salt).decode("ascii")
- chk = ab64_encode(self.checksum).decode("ascii")
- return uh.render_mc3(self.ident, self.rounds, salt, chk)
- def _calc_checksum(self, secret):
- # NOTE: pbkdf2_hmac() will encode secret & salt using UTF8
- return pbkdf2_hmac(self._digest, secret, self.salt, self.rounds, self.checksum_size)
- def create_pbkdf2_hash(hash_name, digest_size, rounds=12000, ident=None, module=__name__):
- """create new Pbkdf2DigestHandler subclass for a specific hash"""
- name = 'pbkdf2_' + hash_name
- if ident is None:
- ident = u("$pbkdf2-%s$") % (hash_name,)
- base = Pbkdf2DigestHandler
- return type(name, (base,), dict(
- __module__=module, # so ABCMeta won't clobber it.
- name=name,
- ident=ident,
- _digest = hash_name,
- default_rounds=rounds,
- checksum_size=digest_size,
- encoded_checksum_size=(digest_size*4+2)//3,
- __doc__="""This class implements a generic ``PBKDF2-HMAC-%(digest)s``-based password hash, and follows the :ref:`password-hash-api`.
- It supports a variable-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
- :type salt: bytes
- :param salt:
- Optional salt bytes.
- If specified, the length must be between 0-1024 bytes.
- If not specified, a %(dsc)d byte salt will be autogenerated (this is recommended).
- :type salt_size: int
- :param salt_size:
- Optional number of bytes to use when autogenerating new salts.
- Defaults to %(dsc)d bytes, but can be any value between 0 and 1024.
- :type rounds: int
- :param rounds:
- Optional number of rounds to use.
- Defaults to %(dr)d, but must be within ``range(1,1<<32)``.
- :type relaxed: bool
- :param relaxed:
- By default, providing an invalid value for one of the other
- keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
- and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
- will be issued instead. Correctable errors include ``rounds``
- that are too small or too large, and ``salt`` strings that are too long.
- .. versionadded:: 1.6
- """ % dict(digest=hash_name.upper(), dsc=base.default_salt_size, dr=rounds)
- ))
- #------------------------------------------------------------------------
- # derived handlers
- #------------------------------------------------------------------------
- pbkdf2_sha1 = create_pbkdf2_hash("sha1", 20, 131000, ident=u("$pbkdf2$"))
- pbkdf2_sha256 = create_pbkdf2_hash("sha256", 32, 29000)
- pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64, 25000)
- ldap_pbkdf2_sha1 = uh.PrefixWrapper("ldap_pbkdf2_sha1", pbkdf2_sha1, "{PBKDF2}", "$pbkdf2$", ident=True)
- ldap_pbkdf2_sha256 = uh.PrefixWrapper("ldap_pbkdf2_sha256", pbkdf2_sha256, "{PBKDF2-SHA256}", "$pbkdf2-sha256$", ident=True)
- ldap_pbkdf2_sha512 = uh.PrefixWrapper("ldap_pbkdf2_sha512", pbkdf2_sha512, "{PBKDF2-SHA512}", "$pbkdf2-sha512$", ident=True)
- #=============================================================================
- # cryptacular's pbkdf2 hash
- #=============================================================================
- # bytes used by cta hash for base64 values 63 & 64
- CTA_ALTCHARS = b"-_"
- class cta_pbkdf2_sha1(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
- """This class implements Cryptacular's PBKDF2-based crypt algorithm, and follows the :ref:`password-hash-api`.
- It supports a variable-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
- :type salt: bytes
- :param salt:
- Optional salt bytes.
- If specified, it may be any length.
- If not specified, a one will be autogenerated (this is recommended).
- :type salt_size: int
- :param salt_size:
- Optional number of bytes to use when autogenerating new salts.
- Defaults to 16 bytes, but can be any value between 0 and 1024.
- :type rounds: int
- :param rounds:
- Optional number of rounds to use.
- Defaults to 60000, must be within ``range(1,1<<32)``.
- :type relaxed: bool
- :param relaxed:
- By default, providing an invalid value for one of the other
- keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
- and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
- will be issued instead. Correctable errors include ``rounds``
- that are too small or too large, and ``salt`` strings that are too long.
- .. versionadded:: 1.6
- """
- #===================================================================
- # class attrs
- #===================================================================
- #--GenericHandler--
- name = "cta_pbkdf2_sha1"
- setting_kwds = ("salt", "salt_size", "rounds")
- ident = u("$p5k2$")
- checksum_size = 20
- # NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a
- # sanity check. underlying algorithm (and reference implementation)
- # allows effectively unbounded values for both of these parameters.
- #--HasSalt--
- default_salt_size = 16
- max_salt_size = 1024
- #--HasRounds--
- default_rounds = pbkdf2_sha1.default_rounds
- min_rounds = 1
- max_rounds = 0xffffffff # setting at 32-bit limit for now
- rounds_cost = "linear"
- #===================================================================
- # formatting
- #===================================================================
- # hash $p5k2$1000$ZxK4ZBJCfQg=$jJZVscWtO--p1-xIZl6jhO2LKR0=
- # ident $p5k2$
- # rounds 1000
- # salt ZxK4ZBJCfQg=
- # chk jJZVscWtO--p1-xIZl6jhO2LKR0=
- # NOTE: rounds in hex
- @classmethod
- def from_string(cls, hash):
- # NOTE: passlib deviation - forbidding zero-padded rounds
- rounds, salt, chk = uh.parse_mc3(hash, cls.ident, rounds_base=16, handler=cls)
- salt = b64decode(salt.encode("ascii"), CTA_ALTCHARS)
- if chk:
- chk = b64decode(chk.encode("ascii"), CTA_ALTCHARS)
- return cls(rounds=rounds, salt=salt, checksum=chk)
- def to_string(self):
- salt = b64encode(self.salt, CTA_ALTCHARS).decode("ascii")
- chk = b64encode(self.checksum, CTA_ALTCHARS).decode("ascii")
- return uh.render_mc3(self.ident, self.rounds, salt, chk, rounds_base=16)
- #===================================================================
- # backend
- #===================================================================
- def _calc_checksum(self, secret):
- # NOTE: pbkdf2_hmac() will encode secret & salt using utf-8
- return pbkdf2_hmac("sha1", secret, self.salt, self.rounds, 20)
- #===================================================================
- # eoc
- #===================================================================
- #=============================================================================
- # dlitz's pbkdf2 hash
- #=============================================================================
- class dlitz_pbkdf2_sha1(uh.HasRounds, uh.HasSalt, uh.GenericHandler):
- """This class implements Dwayne Litzenberger's PBKDF2-based crypt algorithm, and follows the :ref:`password-hash-api`.
- It supports a variable-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
- :type salt: str
- :param salt:
- Optional salt string.
- If specified, it may be any length, but must use the characters in the regexp range ``[./0-9A-Za-z]``.
- If not specified, a 16 character salt will be autogenerated (this is recommended).
- :type salt_size: int
- :param salt_size:
- Optional number of bytes to use when autogenerating new salts.
- Defaults to 16 bytes, but can be any value between 0 and 1024.
- :type rounds: int
- :param rounds:
- Optional number of rounds to use.
- Defaults to 60000, must be within ``range(1,1<<32)``.
- :type relaxed: bool
- :param relaxed:
- By default, providing an invalid value for one of the other
- keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
- and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
- will be issued instead. Correctable errors include ``rounds``
- that are too small or too large, and ``salt`` strings that are too long.
- .. versionadded:: 1.6
- """
- #===================================================================
- # class attrs
- #===================================================================
- #--GenericHandler--
- name = "dlitz_pbkdf2_sha1"
- setting_kwds = ("salt", "salt_size", "rounds")
- ident = u("$p5k2$")
- _stub_checksum = u("0" * 48 + "=")
- # NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a
- # sanity check. underlying algorithm (and reference implementation)
- # allows effectively unbounded values for both of these parameters.
- #--HasSalt--
- default_salt_size = 16
- max_salt_size = 1024
- salt_chars = uh.HASH64_CHARS
- #--HasRounds--
- # NOTE: for security, the default here is set to match pbkdf2_sha1,
- # even though this hash's extra block makes it twice as slow.
- default_rounds = pbkdf2_sha1.default_rounds
- min_rounds = 1
- max_rounds = 0xffffffff # setting at 32-bit limit for now
- rounds_cost = "linear"
- #===================================================================
- # formatting
- #===================================================================
- # hash $p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g
- # ident $p5k2$
- # rounds c
- # salt u9HvcT4d
- # chk Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g
- # rounds in lowercase hex, no zero padding
- @classmethod
- def from_string(cls, hash):
- rounds, salt, chk = uh.parse_mc3(hash, cls.ident, rounds_base=16,
- default_rounds=400, handler=cls)
- return cls(rounds=rounds, salt=salt, checksum=chk)
- def to_string(self):
- rounds = self.rounds
- if rounds == 400:
- rounds = None # omit rounds measurement if == 400
- return uh.render_mc3(self.ident, rounds, self.salt, self.checksum, rounds_base=16)
- def _get_config(self):
- rounds = self.rounds
- if rounds == 400:
- rounds = None # omit rounds measurement if == 400
- return uh.render_mc3(self.ident, rounds, self.salt, None, rounds_base=16)
- #===================================================================
- # backend
- #===================================================================
- def _calc_checksum(self, secret):
- # NOTE: pbkdf2_hmac() will encode secret & salt using utf-8
- salt = self._get_config()
- result = pbkdf2_hmac("sha1", secret, salt, self.rounds, 24)
- return ab64_encode(result).decode("ascii")
- #===================================================================
- # eoc
- #===================================================================
- #=============================================================================
- # crowd
- #=============================================================================
- class atlassian_pbkdf2_sha1(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
- """This class implements the PBKDF2 hash used by Atlassian.
- It supports a fixed-length salt, and a fixed number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
- :type salt: bytes
- :param salt:
- Optional salt bytes.
- If specified, the length must be exactly 16 bytes.
- If not specified, a salt will be autogenerated (this is recommended).
- :type relaxed: bool
- :param relaxed:
- By default, providing an invalid value for one of the other
- keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
- and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
- will be issued instead. Correctable errors include
- ``salt`` strings that are too long.
- .. versionadded:: 1.6
- """
- #--GenericHandler--
- name = "atlassian_pbkdf2_sha1"
- setting_kwds =("salt",)
- ident = u("{PKCS5S2}")
- checksum_size = 32
- #--HasRawSalt--
- min_salt_size = max_salt_size = 16
- @classmethod
- def from_string(cls, hash):
- hash = to_unicode(hash, "ascii", "hash")
- ident = cls.ident
- if not hash.startswith(ident):
- raise uh.exc.InvalidHashError(cls)
- data = b64decode(hash[len(ident):].encode("ascii"))
- salt, chk = data[:16], data[16:]
- return cls(salt=salt, checksum=chk)
- def to_string(self):
- data = self.salt + self.checksum
- hash = self.ident + b64encode(data).decode("ascii")
- return uascii_to_str(hash)
- def _calc_checksum(self, secret):
- # TODO: find out what crowd's policy is re: unicode
- # crowd seems to use a fixed number of rounds.
- # NOTE: pbkdf2_hmac() will encode secret & salt using utf-8
- return pbkdf2_hmac("sha1", secret, self.salt, 10000, 32)
- #=============================================================================
- # grub
- #=============================================================================
- class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
- """This class implements Grub's pbkdf2-hmac-sha512 hash, and follows the :ref:`password-hash-api`.
- It supports a variable-length salt, and a variable number of rounds.
- The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
- :type salt: bytes
- :param salt:
- Optional salt bytes.
- If specified, the length must be between 0-1024 bytes.
- If not specified, a 64 byte salt will be autogenerated (this is recommended).
- :type salt_size: int
- :param salt_size:
- Optional number of bytes to use when autogenerating new salts.
- Defaults to 64 bytes, but can be any value between 0 and 1024.
- :type rounds: int
- :param rounds:
- Optional number of rounds to use.
- Defaults to 19000, but must be within ``range(1,1<<32)``.
- :type relaxed: bool
- :param relaxed:
- By default, providing an invalid value for one of the other
- keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
- and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
- will be issued instead. Correctable errors include ``rounds``
- that are too small or too large, and ``salt`` strings that are too long.
- .. versionadded:: 1.6
- """
- name = "grub_pbkdf2_sha512"
- setting_kwds = ("salt", "salt_size", "rounds")
- ident = u("grub.pbkdf2.sha512.")
- checksum_size = 64
- # NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide a
- # sanity check. the underlying pbkdf2 specifies no bounds for either,
- # and it's not clear what grub specifies.
- default_salt_size = 64
- max_salt_size = 1024
- default_rounds = pbkdf2_sha512.default_rounds
- min_rounds = 1
- max_rounds = 0xffffffff # setting at 32-bit limit for now
- rounds_cost = "linear"
- @classmethod
- def from_string(cls, hash):
- rounds, salt, chk = uh.parse_mc3(hash, cls.ident, sep=u("."),
- handler=cls)
- salt = unhexlify(salt.encode("ascii"))
- if chk:
- chk = unhexlify(chk.encode("ascii"))
- return cls(rounds=rounds, salt=salt, checksum=chk)
- def to_string(self):
- salt = hexlify(self.salt).decode("ascii").upper()
- chk = hexlify(self.checksum).decode("ascii").upper()
- return uh.render_mc3(self.ident, self.rounds, salt, chk, sep=u("."))
- def _calc_checksum(self, secret):
- # TODO: find out what grub's policy is re: unicode
- # NOTE: pbkdf2_hmac() will encode secret & salt using utf-8
- return pbkdf2_hmac("sha512", secret, self.salt, self.rounds, 64)
- #=============================================================================
- # eof
- #=============================================================================
|