diff options
| author | Eli Collins <elic@assurancetechnologies.com> | 2011-06-17 23:26:28 -0400 |
|---|---|---|
| committer | Eli Collins <elic@assurancetechnologies.com> | 2011-06-17 23:26:28 -0400 |
| commit | 4e26763cd57da1e7feeaa5568f535acc85418948 (patch) | |
| tree | bd5a4241e7ecb0af06495fdaf74d89970e3d40a7 | |
| parent | e1cad3f31308c0d6bc2928978a771bfc885a3e5f (diff) | |
| download | passlib-4e26763cd57da1e7feeaa5568f535acc85418948.tar.gz | |
pbkdf2 handlers now py3 compat
| -rw-r--r-- | passlib/handlers/pbkdf2.py | 84 | ||||
| -rw-r--r-- | passlib/tests/test_drivers.py | 28 | ||||
| -rw-r--r-- | passlib/utils/__init__.py | 12 | ||||
| -rw-r--r-- | passlib/utils/handlers.py | 6 |
4 files changed, 76 insertions, 54 deletions
diff --git a/passlib/handlers/pbkdf2.py b/passlib/handlers/pbkdf2.py index 981758d..5283dfb 100644 --- a/passlib/handlers/pbkdf2.py +++ b/passlib/handlers/pbkdf2.py @@ -10,7 +10,8 @@ import logging; log = logging.getLogger(__name__) from warnings import warn #site #libs -from passlib.utils import adapted_b64_encode, adapted_b64_decode, ALL_BYTE_VALUES, handlers as uh +from passlib.utils import adapted_b64_encode, adapted_b64_decode, \ + handlers as uh, to_hash_str, to_unicode, bytes, b from passlib.utils.pbkdf2 import pbkdf2 #pkg #local @@ -66,10 +67,10 @@ class Pbkdf2DigestHandler(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gen raise ValueError("no hash specified") rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name) int_rounds = int(rounds) - if rounds != str(int_rounds): #forbid zero padding, etc. + if rounds != unicode(int_rounds): #forbid zero padding, etc. raise ValueError("invalid %s hash" % (cls.name,)) - raw_salt = adapted_b64_decode(salt) - raw_chk = adapted_b64_decode(chk) if chk else None + raw_salt = adapted_b64_decode(salt.encode("ascii")) + raw_chk = adapted_b64_decode(chk.encode("ascii")) if chk else None return cls( rounds=int_rounds, salt=raw_salt, @@ -78,11 +79,13 @@ class Pbkdf2DigestHandler(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gen ) def to_string(self, withchk=True): - salt = adapted_b64_encode(self.salt) + salt = adapted_b64_encode(self.salt).decode("ascii") if withchk and self.checksum: - return '%s%d$%s$%s' % (self.ident, self.rounds, salt, adapted_b64_encode(self.checksum)) + chk = adapted_b64_encode(self.checksum).decode("ascii") + hash = u'%s%d$%s$%s' % (self.ident, self.rounds, salt, chk) else: - return '%s%d$%s' % (self.ident, self.rounds, salt) + hash = u'%s%d$%s' % (self.ident, self.rounds, salt) + return to_hash_str(hash) def calc_checksum(self, secret): if isinstance(secret, unicode): @@ -93,7 +96,7 @@ def create_pbkdf2_hash(hash_name, digest_size, ident=None): "create new Pbkdf2DigestHandler subclass for a specific hash" name = 'pbkdf2_' + hash_name if ident is None: - ident = "$pbkdf2-%s$" % (hash_name,) + ident = u"$pbkdf2-%s$" % (hash_name,) prf = "hmac-%s" % (hash_name,) base = Pbkdf2DigestHandler return type(name, (base,), dict( @@ -126,7 +129,7 @@ def create_pbkdf2_hash(hash_name, digest_size, ident=None): #--------------------------------------------------------- #derived handlers #--------------------------------------------------------- -pbkdf2_sha1 = create_pbkdf2_hash("sha1", 20, ident="$pbkdf2$") +pbkdf2_sha1 = create_pbkdf2_hash("sha1", 20, ident=u"$pbkdf2$") pbkdf2_sha256 = create_pbkdf2_hash("sha256", 32) pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64) @@ -137,6 +140,10 @@ ldap_pbkdf2_sha512 = uh.PrefixWrapper("ldap_pbkdf2_sha512", pbkdf2_sha512, "{PBK #========================================================= #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`. @@ -164,7 +171,7 @@ class cta_pbkdf2_sha1(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Generic #--GenericHandler-- name = "cta_pbkdf2_sha1" setting_kwds = ("salt", "salt_size", "rounds") - ident = "$p5k2$" + ident = u"$p5k2$" #NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide sanity check. # underlying algorithm (and reference implementation) allow effectively unbounded values for both of these. @@ -201,9 +208,9 @@ class cta_pbkdf2_sha1(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Generic #left-padded with zeroes raise ValueError("invalid cta_pbkdf2_sha1 hash") rounds = int(rounds, 16) - salt = b64decode(salt, "-_") + salt = b64decode(salt.encode("ascii"), CTA_ALTCHARS) if chk: - chk = b64decode(chk, "-_") + chk = b64decode(chk.encode("ascii"), CTA_ALTCHARS) return cls( rounds=rounds, salt=salt, @@ -212,10 +219,12 @@ class cta_pbkdf2_sha1(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Generic ) def to_string(self, withchk=True): - out = '$p5k2$%x$%s' % (self.rounds, b64encode(self.salt, "-_")) + out = u'$p5k2$%x$%s' % (self.rounds, + b64encode(self.salt, CTA_ALTCHARS).decode("ascii")) if withchk and self.checksum: - out = "%s$%s" % (out,b64encode(self.checksum,"-_")) - return out + out = u"%s$%s" % (out, + b64encode(self.checksum, CTA_ALTCHARS).decode("ascii")) + return to_hash_str(out) #========================================================= #backend @@ -259,7 +268,7 @@ class dlitz_pbkdf2_sha1(uh.HasRounds, uh.HasSalt, uh.GenericHandler): #--GenericHandler-- name = "dlitz_pbkdf2_sha1" setting_kwds = ("salt", "salt_size", "rounds") - ident = "$p5k2$" + ident = u"$p5k2$" #NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide sanity check. # underlying algorithm (and reference implementation) allow effectively unbounded values for both of these. @@ -302,14 +311,14 @@ class dlitz_pbkdf2_sha1(uh.HasRounds, uh.HasSalt, uh.GenericHandler): strict=bool(chk), ) - def to_string(self, withchk=True): + def to_string(self, withchk=True, native=True): if self.rounds == 400: - out = '$p5k2$$%s' % (self.salt,) + out = u'$p5k2$$%s' % (self.salt,) else: - out = '$p5k2$%x$%s' % (self.rounds, self.salt) + out = u'$p5k2$%x$%s' % (self.rounds, self.salt) if withchk and self.checksum: - out = "%s$%s" % (out,self.checksum) - return out + out = u"%s$%s" % (out,self.checksum) + return to_hash_str(out) if native else out #========================================================= #backend @@ -317,9 +326,9 @@ class dlitz_pbkdf2_sha1(uh.HasRounds, uh.HasSalt, uh.GenericHandler): def calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") - salt = self.to_string(withchk=False) + salt = self.to_string(withchk=False, native=False).encode("ascii") result = pbkdf2(secret, salt, self.rounds, 24, "hmac-sha1") - return adapted_b64_encode(result) + return adapted_b64_encode(result).decode("ascii") #========================================================= #eoc @@ -343,10 +352,10 @@ class atlassian_pbkdf2_sha1(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler) #--GenericHandler-- name = "atlassian_pbkdf2_sha1" setting_kwds =("salt",) - ident = "{PKCS5S2}" + ident = u"{PKCS5S2}" checksum_size = 32 - _stub_checksum = "\x00" * 32 + _stub_checksum = b("\x00") * 32 #--HasRawSalt-- min_salt_size = max_salt_size = 16 @@ -355,18 +364,19 @@ class atlassian_pbkdf2_sha1(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler) def from_string(cls, hash): if not hash: raise ValueError("no hash specified") - if isinstance(hash, unicode): - hash = hash.encode("ascii") + if isinstance(hash, bytes): + hash = hash.decode("ascii") ident = cls.ident if not hash.startswith(ident): raise ValueError("invalid %s hash" % (cls.name,)) - data = b64decode(hash[len(ident):]) + data = b64decode(hash[len(ident):].encode("ascii")) salt, chk = data[:16], data[16:] return cls(salt=salt, checksum=chk, strict=True) def to_string(self): data = self.salt + (self.checksum or self._stub_checksum) - return self.ident + b64encode(data) + hash = self.ident + b64encode(data).decode("ascii") + return to_hash_str(hash) def calc_checksum(self, secret): #TODO: find out what crowd's policy is re: unicode @@ -401,7 +411,7 @@ class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gene name = "grub_pbkdf2_sha512" setting_kwds = ("salt", "salt_size", "rounds") - ident = "grub.pbkdf2.sha512." + ident = u"grub.pbkdf2.sha512." #NOTE: max_salt_size and max_rounds are arbitrarily chosen to provide sanity check. # the underlying pbkdf2 specifies no bounds for either, @@ -420,12 +430,12 @@ class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gene def from_string(cls, hash): if not hash: raise ValueError("no hash specified") - rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name, sep=".") + rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name, sep=u".") int_rounds = int(rounds) if rounds != str(int_rounds): #forbid zero padding, etc. raise ValueError("invalid %s hash" % (cls.name,)) - raw_salt = unhexlify(salt) - raw_chk = unhexlify(chk) if chk else None + raw_salt = unhexlify(salt.encode("ascii")) + raw_chk = unhexlify(chk.encode("ascii")) if chk else None return cls( rounds=int_rounds, salt=raw_salt, @@ -434,11 +444,13 @@ class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gene ) def to_string(self, withchk=True): - salt = hexlify(self.salt).upper() + salt = hexlify(self.salt).decode("ascii").upper() if withchk and self.checksum: - return '%s%d.%s.%s' % (self.ident, self.rounds, salt, hexlify(self.checksum).upper()) + chk = hexlify(self.checksum).decode("ascii").upper() + hash = u'%s%d.%s.%s' % (self.ident, self.rounds, salt, chk) else: - return '%s%d.%s' % (self.ident, self.rounds, salt) + hash = u'%s%d.%s' % (self.ident, self.rounds, salt) + return to_hash_str(hash) def calc_checksum(self, secret): #TODO: find out what grub's policy is re: unicode diff --git a/passlib/tests/test_drivers.py b/passlib/tests/test_drivers.py index 49234c5..65589a6 100644 --- a/passlib/tests/test_drivers.py +++ b/passlib/tests/test_drivers.py @@ -519,11 +519,11 @@ from passlib.handlers import pbkdf2 as pk2 class AtlassianPbkdf2Sha1Test(HandlerCase): handler = pk2.atlassian_pbkdf2_sha1 - known_correct_hashes = ( + known_correct_hashes = [ ("admin", '{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/p'), (u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2', "{PKCS5S2}cE9Yq6Am5tQGdHSHhky2XLeOnURwzaLBG2sur7FHKpvy2u0qDn6GcVGRjlmJoIUy"), - ) + ] known_malformed_hashes = [ #bad char @@ -538,26 +538,26 @@ class AtlassianPbkdf2Sha1Test(HandlerCase): class Pbkdf2Sha1Test(HandlerCase): handler = pk2.pbkdf2_sha1 - known_correct_hashes = ( + known_correct_hashes = [ ("password", '$pbkdf2$1212$OB.dtnSEXZK8U5cgxU/GYQ$y5LKPOplRmok7CZp/aqVDVg8zGI'), (u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2', '$pbkdf2$1212$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc'), - ) + ] class Pbkdf2Sha256Test(HandlerCase): handler = pk2.pbkdf2_sha256 - known_correct_hashes = ( + known_correct_hashes = [ ("password", '$pbkdf2-sha256$1212$4vjV83LKPjQzk31VI4E0Vw$hsYF68OiOUPdDZ1Fg.fJPeq1h/gXXY7acBp9/6c.tmQ' ), (u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2', '$pbkdf2-sha256$1212$3SABFJGDtyhrQMVt1uABPw$WyaUoqCLgvz97s523nF4iuOqZNbp5Nt8do/cuaa7AiI' ), - ) + ] class Pbkdf2Sha512Test(HandlerCase): handler = pk2.pbkdf2_sha512 - known_correct_hashes = ( + known_correct_hashes = [ ("password", '$pbkdf2-sha512$1212$RHY0Fr3IDMSVO/RSZyb5ow$eNLfBK.eVozomMr.1gYa1' '7k9B7KIK25NOEshvhrSX.esqY3s.FvWZViXz4KoLlQI.BzY/YTNJOiKc5gBYFYGww' @@ -566,11 +566,11 @@ class Pbkdf2Sha512Test(HandlerCase): '$pbkdf2-sha512$1212$KkbvoKGsAIcF8IslDR6skQ$8be/PRmd88Ps8fmPowCJt' 'tH9G3vgxpG.Krjt3KT.NP6cKJ0V4Prarqf.HBwz0dCkJ6xgWnSj2ynXSV7MlvMa8Q' ), - ) + ] class CtaPbkdf2Sha1Test(HandlerCase): handler = pk2.cta_pbkdf2_sha1 - known_correct_hashes = ( + known_correct_hashes = [ #test vectors from original implementation (u"hashy the \N{SNOWMAN}", '$p5k2$1000$ZxK4ZBJCfQg=$jJZVscWtO--p1-xIZl6jhO2LKR0='), @@ -578,11 +578,11 @@ class CtaPbkdf2Sha1Test(HandlerCase): ("password", "$p5k2$1$$h1TDLGSw9ST8UMAPeIE13i0t12c="), (u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2', "$p5k2$4321$OTg3NjU0MzIx$jINJrSvZ3LXeIbUdrJkRpN62_WQ="), - ) + ] class DlitzPbkdf2Sha1Test(HandlerCase): handler = pk2.dlitz_pbkdf2_sha1 - known_correct_hashes = ( + known_correct_hashes = [ #test vectors from original implementation ('cloadm', '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql'), ('gnu', '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g'), @@ -590,11 +590,11 @@ class DlitzPbkdf2Sha1Test(HandlerCase): ('spam', '$p5k2$3e8$H0NX9mT/$wk/sE8vv6OMKuMaqazCJYDSUhWY9YB2J'), (u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2', '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ'), - ) + ] class GrubPbkdf2Sha512Test(HandlerCase): handler = pk2.grub_pbkdf2_sha512 - known_correct_hashes = ( + known_correct_hashes = [ #test vectors generated from cmd line tool #salt=32 bytes @@ -614,7 +614,7 @@ class GrubPbkdf2Sha512Test(HandlerCase): '2D3256ECC9F765D84956FC5CA5C4B6FD711AA285F0A04DCF4' '634083F9A20F4B6F339A52FBD6BED618E527B'), - ) + ] #========================================================= #PHPass Portable Crypt diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py index e444383..ab5826b 100644 --- a/passlib/utils/__init__.py +++ b/passlib/utils/__init__.py @@ -498,6 +498,10 @@ def xor_bytes(left, right): #================================================================================= #alt base64 encoding #================================================================================= +_A64_ALTCHARS = b("./") +_A64_STRIP = b("=\n") +_A64_PAD1 = b("=") +_A64_PAD2 = b("==") def adapted_b64_encode(data): """encode using variant of base64 @@ -508,7 +512,7 @@ def adapted_b64_encode(data): it is primarily used for by passlib's custom pbkdf2 hashes. """ - return b64encode(data, "./").strip("=\n") + return b64encode(data, _A64_ALTCHARS).strip(_A64_STRIP) def adapted_b64_decode(data, sixthree="."): """decode using variant of base64 @@ -521,13 +525,13 @@ def adapted_b64_decode(data, sixthree="."): """ off = len(data) % 4 if off == 0: - return b64decode(data, "./") + return b64decode(data, _A64_ALTCHARS) elif off == 1: raise ValueError("invalid bas64 input") elif off == 2: - return b64decode(data + "==", "./") + return b64decode(data + _A64_PAD2, _A64_ALTCHARS) else: - return b64decode(data + "=", "./") + return b64decode(data + _A64_PAD1, _A64_ALTCHARS) #================================================================================= #randomness diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py index b06a9eb..c15deb6 100644 --- a/passlib/utils/handlers.py +++ b/passlib/utils/handlers.py @@ -366,6 +366,12 @@ class GenericHandler(object): should return native string type (ascii-bytes under python 2, unicode under python 3) """ + #NOTE: documenting some non-standardized but common kwd flags + # that passlib to_string() method may have + # + # native=True -- if false, return unicode under py2 -- ignored under py3 + # withchk=True -- if false, omit checksum portion of hash + # raise NotImplementedError("%s must implement from_string()" % (type(self),)) ##def to_config_string(self): |
