summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2012-04-10 12:33:06 -0400
committerEli Collins <elic@assurancetechnologies.com>2012-04-10 12:33:06 -0400
commitd1aaad2741b18bf582e938c03230a84e158a7445 (patch)
tree43ed0413f6cc62280f97cf38690495606a673c90
parentf74deb32c6af1eac658e7d30ee3fe20e8fdf141c (diff)
downloadpasslib-d1aaad2741b18bf582e938c03230a84e158a7445.tar.gz
parse_mc3/render_mc3 helpers now handle rounds str<->int, consolidated a bunch of redundant code
-rw-r--r--admin/benchmarks.py2
-rw-r--r--passlib/handlers/pbkdf2.py96
-rw-r--r--passlib/handlers/sha1_crypt.py13
-rw-r--r--passlib/utils/handlers.py82
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