summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2012-04-27 02:06:10 -0400
committerEli Collins <elic@assurancetechnologies.com>2012-04-27 02:06:10 -0400
commita27f68103a29207a686d7bb6cadeca7a0c16ed1a (patch)
treee6da1e90019de358e771828332d4171511cc18f3
parentd8aad831a7579bfb6a8829e934695477dcb3e833 (diff)
downloadpasslib-a27f68103a29207a686d7bb6cadeca7a0c16ed1a.tar.gz
added experimental GenericHandler.bitsize() method, which estimates bitsize of hash components
-rw-r--r--passlib/handlers/sha2_crypt.py8
-rw-r--r--passlib/utils/handlers.py79
2 files changed, 78 insertions, 9 deletions
diff --git a/passlib/handlers/sha2_crypt.py b/passlib/handlers/sha2_crypt.py
index 29a18de..2069892 100644
--- a/passlib/handlers/sha2_crypt.py
+++ b/passlib/handlers/sha2_crypt.py
@@ -263,12 +263,14 @@ class _SHA2_Common(uh.HasManyBackends, uh.HasRounds, uh.HasSalt,
#=========================================================
# methods
#=========================================================
- implicit_rounds = True
+ implicit_rounds = False
def __init__(self, implicit_rounds=None, **kwds):
- if implicit_rounds is not None:
- self.implicit_rounds = implicit_rounds
super(_SHA2_Common, self).__init__(**kwds)
+ # if user calls encrypt() w/ 5000 rounds, default to compact form.
+ if implicit_rounds is None:
+ implicit_rounds = (self.use_defaults and self.rounds == 5000)
+ self.implicit_rounds = implicit_rounds
@classmethod
def from_string(cls, hash):
diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py
index e956cc3..099fd84 100644
--- a/passlib/utils/handlers.py
+++ b/passlib/utils/handlers.py
@@ -70,6 +70,17 @@ UC_HEX_CHARS = UPPER_HEX_CHARS
LC_HEX_CHARS = LOWER_HEX_CHARS
#=========================================================
+# support functions
+#=========================================================
+def _bitsize(count, chars):
+ """helper for bitsize() methods"""
+ if chars and count:
+ import math
+ return int(count * math.log(len(chars), 2))
+ else:
+ return 0
+
+#=========================================================
# parsing helpers
#=========================================================
_UDOLLAR = u("$")
@@ -585,8 +596,22 @@ class GenericHandler(object):
## currently only provided by bcrypt() to fix an historical passlib issue.
## """
+ @classmethod
+ def bitsize(cls, **kwds):
+ "[experimental method] return info about bitsizes of hash"
+ try:
+ info = super(GenericHandler, cls).bitsize(**kwds)
+ except AttributeError:
+ info = {}
+ cc = ALL_BYTES if cls._checksum_is_bytes else cls.checksum_chars
+ if cls.checksum_size and cc:
+ # FIXME: this may overestimate size due to padding bits (e.g. bcrypt)
+ # FIXME: this will be off by 1 for case-insensitive hashes.
+ info['checksum'] = _bitsize(cls.checksum_size, cc)
+ return info
+
#=========================================================
- #eoc
+ # eoc
#=========================================================
class StaticHandler(GenericHandler):
@@ -705,13 +730,22 @@ class HasUserContext(GenericHandler):
@classmethod
def verify(cls, secret, hash, user=None, **context):
- return super(HasUserContext, cls).verify(secret, hash, user=user,
- **context)
+ return super(HasUserContext, cls).verify(secret, hash, user=user, **context)
@classmethod
def genhash(cls, secret, config, user=None, **context):
- return super(HasUserContext, cls).genhash(secret, config, user=user,
- **context)
+ return super(HasUserContext, cls).genhash(secret, config, user=user, **context)
+
+ # XXX: how to guess the entropy of a username?
+ # most of these hashes are for a system (e.g. Oracle)
+ # which has a few *very common* names and thus really low entropy;
+ # while the rest are slightly less predictable.
+ # need to find good reference about this.
+ ##@classmethod
+ ##def bitsize(cls, **kwds):
+ ## info = super(HasUserContext, cls).bitsize(**kwds)
+ ## info['user'] = xxx
+ ## return info
#-----------------------------------------------------
# checksum mixins
@@ -1018,6 +1052,17 @@ class HasSalt(GenericHandler):
"""
return getrandstr(rng, self.default_salt_chars, salt_size)
+ @classmethod
+ def bitsize(cls, salt_size=None, **kwds):
+ "[experimental method] return info about bitsizes of hash"
+ info = super(HasSalt, cls).bitsize(**kwds)
+ if salt_size is None:
+ salt_size = cls.default_salt_size
+ # FIXME: this may overestimate size due to padding bits
+ # FIXME: this will be off by 1 for case-insensitive hashes.
+ info['salt'] = _bitsize(salt_size, cls.default_salt_chars)
+ return info
+
#=========================================================
#eoc
#=========================================================
@@ -1101,7 +1146,7 @@ class HasRounds(GenericHandler):
#=========================================================
min_rounds = 0
max_rounds = None
- defaults_rounds = None
+ default_rounds = None
rounds_cost = "linear" # default to the common case
#=========================================================
@@ -1171,6 +1216,28 @@ class HasRounds(GenericHandler):
return rounds
+ @classmethod
+ def bitsize(cls, rounds=None, vary_rounds=.1, **kwds):
+ "[experimental method] return info about bitsizes of hash"
+ info = super(HasRounds, cls).bitsize(**kwds)
+ # NOTE: this essentially estimates how many bits of "salt"
+ # can be added by varying the rounds value just a little bit.
+ if cls.rounds_cost != "log2":
+ # assume rounds can be randomized within the range
+ # rounds*(1-vary_rounds) ... rounds*(1+vary_rounds)
+ # then this can be used to encode
+ # log2(rounds*(1+vary_rounds)-rounds*(1-vary_rounds))
+ # worth of salt-like bits. this works out to
+ # 1+log2(rounds*vary_rounds)
+ import math
+ if rounds is None:
+ rounds = cls.default_rounds
+ info['rounds'] = max(0, int(1+math.log(rounds*vary_rounds,2)))
+ ## else: # log2 rounds
+ # all bits of the rounds value are critical to choosing
+ # the time-cost, and can't be randomized.
+ return info
+
#=========================================================
#eoc
#=========================================================