diff options
| author | Eli Collins <elic@assurancetechnologies.com> | 2012-04-10 12:33:06 -0400 |
|---|---|---|
| committer | Eli Collins <elic@assurancetechnologies.com> | 2012-04-10 12:33:06 -0400 |
| commit | d1aaad2741b18bf582e938c03230a84e158a7445 (patch) | |
| tree | 43ed0413f6cc62280f97cf38690495606a673c90 | |
| parent | f74deb32c6af1eac658e7d30ee3fe20e8fdf141c (diff) | |
| download | passlib-d1aaad2741b18bf582e938c03230a84e158a7445.tar.gz | |
parse_mc3/render_mc3 helpers now handle rounds str<->int, consolidated a bunch of redundant code
| -rw-r--r-- | admin/benchmarks.py | 2 | ||||
| -rw-r--r-- | passlib/handlers/pbkdf2.py | 96 | ||||
| -rw-r--r-- | passlib/handlers/sha1_crypt.py | 13 | ||||
| -rw-r--r-- | passlib/utils/handlers.py | 82 |
4 files changed, 102 insertions, 91 deletions
diff --git a/admin/benchmarks.py b/admin/benchmarks.py index bdf2fdb..91c58e1 100644 --- a/admin/benchmarks.py +++ b/admin/benchmarks.py @@ -50,7 +50,7 @@ class BlankHandler(uh.HasRounds, uh.HasSalt, uh.GenericHandler): @classmethod def from_string(cls, hash): r,s,c = uh.parse_mc3(hash, cls.ident, handler=cls) - return cls(rounds=int(r), salt=s, checksum=c) + return cls(rounds=r, salt=s, checksum=c) def to_string(self): return uh.render_mc3(self.ident, self.rounds, self.salt, self.checksum) diff --git a/passlib/handlers/pbkdf2.py b/passlib/handlers/pbkdf2.py index 5c4b38a..191e673 100644 --- a/passlib/handlers/pbkdf2.py +++ b/passlib/handlers/pbkdf2.py @@ -65,28 +65,19 @@ class Pbkdf2DigestHandler(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gen @classmethod def from_string(cls, hash): - if not hash: - raise ValueError("no hash specified") rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls) - int_rounds = int(rounds) - if rounds != unicode(int_rounds): #forbid zero padding, etc. - raise uh.exc.ZeroPaddedRoundsError(cls) - raw_salt = ab64_decode(salt.encode("ascii")) - raw_chk = ab64_decode(chk.encode("ascii")) if chk else None - return cls( - rounds=int_rounds, - salt=raw_salt, - checksum=raw_chk, - ) + 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, withchk=True): salt = ab64_encode(self.salt).decode("ascii") if withchk and self.checksum: chk = ab64_encode(self.checksum).decode("ascii") - hash = u('%s%d$%s$%s') % (self.ident, self.rounds, salt, chk) else: - hash = u('%s%d$%s') % (self.ident, self.rounds, salt) - return uascii_to_str(hash) + chk = None + return uh.render_mc3(self.ident, self.rounds, salt, chk) def _calc_checksum(self, secret): if isinstance(secret, unicode): @@ -201,30 +192,20 @@ class cta_pbkdf2_sha1(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Generic @classmethod def from_string(cls, hash): - if not hash: - raise uh.exc.MissingHashError(cls) - rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name) - if rounds.startswith("0"): - #passlib deviation: forbidding - #left-padded with zeroes - raise uh.exc.ZeroPaddedRoundsError(cls) - rounds = int(rounds, 16) + # 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, - ) + return cls(rounds=rounds, salt=salt, checksum=chk) def to_string(self, withchk=True): - hash = u('$p5k2$%x$%s') % (self.rounds, - b64encode(self.salt, CTA_ALTCHARS).decode("ascii")) + salt = b64encode(self.salt, CTA_ALTCHARS).decode("ascii") if withchk and self.checksum: - hash = u("%s$%s") % (hash, - b64encode(self.checksum, CTA_ALTCHARS).decode("ascii")) - return uascii_to_str(hash) + chk = b64encode(self.checksum, CTA_ALTCHARS).decode("ascii") + else: + chk = None + return uh.render_mc3(self.ident, self.rounds, salt, chk, rounds_base=16) #========================================================= #backend @@ -300,24 +281,17 @@ class dlitz_pbkdf2_sha1(uh.HasRounds, uh.HasSalt, uh.GenericHandler): def from_string(cls, hash): if not hash: raise uh.exc.MissingHashError(cls) - rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls) - if rounds.startswith("0"): #zero not allowed, nor left-padded with zeroes - raise uh.exc.ZeroPaddedRoundsError(cls) - rounds = int(rounds, 16) if rounds else 400 - return cls( - rounds=rounds, - salt=salt, - checksum=chk, - ) + 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, withchk=True): - if self.rounds == 400: - hash = u('$p5k2$$%s') % (self.salt,) - else: - hash = u('$p5k2$%x$%s') % (self.rounds, self.salt) - if withchk and self.checksum: - hash = u("%s$%s") % (hash,self.checksum) - return uascii_to_str(hash) + rounds = self.rounds + if rounds == 400: + rounds = None # omit rounds measurement if == 400 + return uh.render_mc3(self.ident, rounds, self.salt, + checksum=self.checksum if withchk else None, + rounds_base=16) #========================================================= #backend @@ -427,28 +401,20 @@ class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gene @classmethod def from_string(cls, hash): - if not hash: - raise uh.exc.MissingHashError(cls) - rounds, salt, chk = uh.parse_mc3(hash, cls.ident, sep=u("."), handler=cls) - int_rounds = int(rounds) - if rounds != str(int_rounds): #forbid zero padding, etc. - raise uh.exc.ZeroPaddedRoundsError(cls) - raw_salt = unhexlify(salt.encode("ascii")) - raw_chk = unhexlify(chk.encode("ascii")) if chk else None - return cls( - rounds=int_rounds, - salt=raw_salt, - checksum=raw_chk, - ) + 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, withchk=True): salt = hexlify(self.salt).decode("ascii").upper() if withchk and self.checksum: chk = hexlify(self.checksum).decode("ascii").upper() - hash = u('%s%d.%s.%s') % (self.ident, self.rounds, salt, chk) else: - hash = u('%s%d.%s') % (self.ident, self.rounds, salt) - return uascii_to_str(hash) + chk = None + 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 diff --git a/passlib/handlers/sha1_crypt.py b/passlib/handlers/sha1_crypt.py index fd2f502..2bed9e8 100644 --- a/passlib/handlers/sha1_crypt.py +++ b/passlib/handlers/sha1_crypt.py @@ -81,19 +81,10 @@ class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler @classmethod def from_string(cls, hash): rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls) - if rounds.startswith("0"): - raise uh.exc.ZeroPaddedRoundsError(cls) - return cls( - rounds=int(rounds), - salt=salt, - checksum=chk, - ) + return cls(rounds=rounds, salt=salt, checksum=chk) def to_string(self): - hash = u("$sha1$%d$%s") % (self.rounds, self.salt) - if self.checksum: - hash += u("$") + self.checksum - return uascii_to_str(hash) + return uh.render_mc3(self.ident, self.rounds, self.salt, self.checksum) #========================================================= #backend diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py index bb7eadb..abd975d 100644 --- a/passlib/utils/handlers.py +++ b/passlib/utils/handlers.py @@ -22,7 +22,8 @@ from passlib.utils import classproperty, consteq, getrandstr, getrandbytes,\ is_crypt_handler, deprecated_function, to_unicode, \ MAX_PASSWORD_SIZE from passlib.utils.compat import b, join_byte_values, bytes, irange, u, \ - uascii_to_str, join_unicode, unicode, str_to_uascii + uascii_to_str, join_unicode, unicode, str_to_uascii, \ + join_unicode # local __all__ = [ # helpers for implementing MCF handlers @@ -108,19 +109,27 @@ def parse_mc2(hash, prefix, sep=_UDOLLAR, handler=None): else: raise exc.MalformedHashError(handler) -def parse_mc3(hash, prefix, sep=_UDOLLAR, handler=None): +def parse_mc3(hash, prefix, sep=_UDOLLAR, rounds_base=10, + default_rounds=None, handler=None): """parse hash using 3-part modular crypt format. this expects a hash of the format :samp:`{prefix}[{rounds}$]{salt}[${checksum}]`, such as sha1_crypt, and parses it into rounds / salt / checksum portions. + tries to convert the rounds to an integer, + and throws error if it has zero-padding. :arg hash: the hash to parse (bytes or unicode) :arg prefix: the identifying prefix (unicode) :param sep: field separator (unicode, defaults to ``$``). + :param rounds_base: + the numeric base the rounds are encoded in (defaults to base 10). + :param default_rounds: + the default rounds value to return if the rounds field was omitted. + if this is ``None`` (the default), the rounds field is *required*. :param handler: handler class to pass to error constructors. :returns: - a ``(rounds : str, salt, chk | None)`` tuple. + a ``(rounds : int, salt, chk | None)`` tuple. """ # detect prefix if not hash: @@ -136,31 +145,76 @@ def parse_mc3(hash, prefix, sep=_UDOLLAR, handler=None): parts = hash[len(prefix):].split(sep) if len(parts) == 3: rounds, salt, chk = parts - return rounds, salt, chk or None elif len(parts) == 2: rounds, salt = parts - return rounds, salt, None + chk = None else: raise exc.MalformedHashError(handler) + # validate & parse rounds portion + if rounds.startswith(_UZERO) and rounds != _UZERO: + raise exc.ZeroPaddedRoundsError(handler) + elif rounds: + rounds = int(rounds, rounds_base) + elif default_rounds is None: + raise exc.MalformedHashError(handler, "missing rounds field") + else: + rounds = default_rounds + + # return result + return rounds, salt, chk or None + #===================================================== #formatting helpers #===================================================== def render_mc2(ident, salt, checksum, sep=u("$")): - "format hash using 2-part modular crypt format; inverse of parse_mc2" + """format hash using 2-part modular crypt format; inverse of parse_mc2() + + returns native string with format :samp:`{ident}{salt}[${checksum}]`, + such as used by md5_crypt. + + :arg ident: identifier prefix (unicode) + :arg salt: encoded salt (unicode) + :arg checksum: encoded checksum (unicode or None) + :param sep: separator char (unicode, defaults to ``$``) + + :returns: + config or hash (native str) + """ if checksum: - hash = u("%s%s%s%s") % (ident, salt, sep, checksum) + parts = [ident, salt, sep, checksum] else: - hash = u("%s%s") % (ident, salt) - return uascii_to_str(hash) + parts = [ident, salt] + return uascii_to_str(join_unicode(parts)) + +def render_mc3(ident, rounds, salt, checksum, sep=u("$"), rounds_base=10): + """format hash using 3-part modular crypt format; inverse of parse_mc3() + + returns native string with format :samp:`{ident}[{rounds}$]{salt}[${checksum}]`, + such as used by sha1_crypt. + + :arg ident: identifier prefix (unicode) + :arg rounds: rounds field (int or None) + :arg salt: encoded salt (unicode) + :arg checksum: encoded checksum (unicode or None) + :param sep: separator char (unicode, defaults to ``$``) + :param rounds_base: base to encode rounds value (defaults to base 10) -def render_mc3(ident, rounds, salt, checksum, sep=u("$")): - "format hash using 3-part modular crypt format; inverse of parse_mc3" + :returns: + config or hash (native str) + """ + if rounds is None: + rounds = u('') + elif rounds_base == 16: + rounds = u("%x") % rounds + else: + assert rounds_base == 10 + rounds = unicode(rounds) if checksum: - hash = u("%s%s%s%s%s%s") % (ident, rounds, sep, salt, sep, checksum) + parts = [ident, rounds, sep, salt, sep, checksum] else: - hash = u("%s%s%s%s") % (ident, rounds, sep, salt) - return uascii_to_str(hash) + parts = [ident, rounds, sep, salt] + return uascii_to_str(join_unicode(parts)) #===================================================== #GenericHandler |
