diff options
| -rw-r--r-- | passlib/tests/test_utils.py | 283 | ||||
| -rw-r--r-- | passlib/utils/__init__.py | 97 | ||||
| -rw-r--r-- | passlib/utils/md4.py | 12 | ||||
| -rw-r--r-- | passlib/utils/pbkdf2.py | 56 |
4 files changed, 317 insertions, 131 deletions
diff --git a/passlib/tests/test_utils.py b/passlib/tests/test_utils.py index e43b181..a541857 100644 --- a/passlib/tests/test_utils.py +++ b/passlib/tests/test_utils.py @@ -11,18 +11,25 @@ import warnings #site #pkg #module -from passlib import utils from passlib.context import CryptContext -from passlib.utils import h64, des, Undef, sys_bits, b +from passlib import utils +from passlib.utils import h64, des, Undef, sys_bits, bytes, b, \ + native_str, to_bytes, to_unicode, to_native_str, \ + is_same_codec from passlib.utils.md4 import md4 -from passlib.tests.utils import TestCase, Params as ak, enable_option, catch_warnings +from passlib.tests.utils import TestCase, Params as ak, \ + enable_option, catch_warnings, SkipTest -hb = unhexlify #'hex bytes' +def hb(source): + return unhexlify(b(source)) #========================================================= #byte funcs #========================================================= -class UtilsTest(TestCase): +class MiscTest(TestCase): + "tests various parts of utils module" + + #NOTE: could test xor_bytes(), but it's exercised well enough by pbkdf2 test def test_undef(self): "test Undef singleton" @@ -35,16 +42,19 @@ class UtilsTest(TestCase): self.assertTrue(Undef!=True,) def test_getrandbytes(self): + "test getrandbytes()" def f(*a,**k): return utils.getrandbytes(utils.rng, *a, **k) self.assertEqual(len(f(0)), 0) a = f(10) b = f(10) + self.assertIsInstance(a, bytes) self.assertEqual(len(a), 10) self.assertEqual(len(b), 10) self.assertNotEqual(a, b) def test_getrandstr(self): + "test getrandstr()" def f(*a,**k): return utils.getrandstr(utils.rng, *a, **k) @@ -61,20 +71,31 @@ class UtilsTest(TestCase): self.assertEqual(f('a',5), 'aaaaa') #letters - a = f('abc', 16) - b = f('abc', 16) - self.assertNotEqual(a,b) - self.assertEqual(sorted(set(a)), ['a','b','c']) - + x = f(u'abc', 16) + y = f(u'abc', 16) + self.assertIsInstance(x, unicode) + self.assertNotEqual(x,y) + self.assertEqual(sorted(set(x)), [u'a',u'b',u'c']) + + #bytes + x = f(b('abc'), 16) + y = f(b('abc'), 16) + self.assertIsInstance(x, bytes) + self.assertNotEqual(x,y) + #NOTE: decoding this due to py3 bytes + self.assertEqual(sorted(set(x.decode("ascii"))), [u'a',u'b',u'c']) + #generate_password self.assertEqual(len(utils.generate_password(15)), 15) def test_is_crypt_context(self): + "test is_crypt_context()" cc = CryptContext(["des_crypt"]) self.assertTrue(utils.is_crypt_context(cc)) self.assertFalse(not utils.is_crypt_context(cc)) def test_genseed(self): + "test genseed()" rng = utils.random.Random(utils.genseed()) a = rng.randint(0, 100000) @@ -86,6 +107,120 @@ class UtilsTest(TestCase): rng.seed(utils.genseed(rng)) #========================================================= +#byte/unicode helpers +#========================================================= +class CodecTest(TestCase): + "tests bytes/unicode helpers in passlib.utils" + + def test_bytes(self): + "test b() helper, bytes and native_str types" + # Py2k # + self.assertIs(bytes, type('')) + self.assertIs(native_str, bytes) + # Py3k # + #self.assertIs(bytes, type(b'')) + #self.assertIs(native_str, unicode) + # end Py3k # + + self.assertIsInstance(b(''), bytes) + self.assertIsInstance(b('\x00\xff'), bytes) + # Py2k # + self.assertEqual(b('\x00\xff'), "\x00\xff") + # Py3k # + #self.assertEqual(b('\x00\xff'), b"\x00\xff") + # end Py3k # + + def test_to_bytes(self): + "test to_bytes()" + + #check unicode inputs + self.assertEqual(to_bytes(u'abc'), b('abc')) + self.assertEqual(to_bytes(u'\x00\xff'), b('\x00\xc3\xbf')) + + #check unicode w/ encodings + self.assertEqual(to_bytes(u'\x00\xff', 'latin-1'), b('\x00\xff')) + self.assertRaises(ValueError, to_bytes, u'\x00\xff', 'ascii') + self.assertRaises(TypeError, to_bytes, u'abc', None) + + #check bytes inputs + self.assertEqual(to_bytes(b('abc')), b('abc')) + self.assertEqual(to_bytes(b('\x00\xff')), b('\x00\xff')) + self.assertEqual(to_bytes(b('\x00\xc3\xbf')), b('\x00\xc3\xbf')) + + #check byte inputs ignores enocding + self.assertEqual(to_bytes(b('\x00\xc3\xbf'), "latin-1"), + b('\x00\xc3\xbf')) + self.assertEqual(to_bytes(b('\x00\xc3\xbf'), None, "utf-8"), + b('\x00\xc3\xbf')) + + #check bytes transcoding + self.assertEqual(to_bytes(b('\x00\xc3\xbf'), "latin-1", "utf-8"), + b('\x00\xff')) + + #check other + self.assertRaises(TypeError, to_bytes, None) + + def test_to_unicode(self): + "test to_unicode()" + + #check unicode inputs + self.assertEqual(to_unicode(u'abc'), u'abc') + self.assertEqual(to_unicode(u'\x00\xff'), u'\x00\xff') + + #check unicode input ignores encoding + self.assertEqual(to_unicode(u'\x00\xff', None), u'\x00\xff') + self.assertEqual(to_unicode(u'\x00\xff', "ascii"), u'\x00\xff') + + #check bytes input + self.assertEqual(to_unicode(b('abc')), u'abc') + self.assertEqual(to_unicode(b('\x00\xc3\xbf')), u'\x00\xff') + self.assertEqual(to_unicode(b('\x00\xff'), 'latin-1'), + u'\x00\xff') + self.assertRaises(ValueError, to_unicode, b('\x00\xff')) + self.assertRaises(TypeError, to_unicode, b('\x00\xff'), None) + + #check other + self.assertRaises(TypeError, to_unicode, None) + + def test_to_native_str(self): + "test to_native_str()" + + self.assertEqual(to_native_str(u'abc'), 'abc') + self.assertEqual(to_native_str(b('abc')), 'abc') + self.assertRaises(TypeError, to_native_str, None) + + # Py2k # + self.assertEqual(to_native_str(u'\x00\xff'), b('\x00\xc3\xbf')) + self.assertEqual(to_native_str(b('\x00\xc3\xbf')), b('\x00\xc3\xbf')) + self.assertEqual(to_native_str(u'\x00\xff', 'latin-1'), + b('\x00\xff')) + self.assertEqual(to_native_str(b('\x00\xff'), 'latin-1'), + b('\x00\xff')) + + # Py3k # + #self.assertEqual(to_native_str(u'\x00\xff'), '\x00\xff') + #self.assertEqual(to_native_str(b('\x00\xc3\xbf')), '\x00\xff') + #self.assertEqual(to_native_str(u'\x00\xff', 'latin-1'), + # '\x00\xff') + #self.assertEqual(to_native_str(b('\x00\xff'), 'latin-1'), + # '\x00\xff') + # + # end Py3k # + def test_is_same_codec(self): + "test is_same_codec()" + self.assertTrue(is_same_codec(None, None)) + self.assertFalse(is_same_codec(None, 'ascii')) + + self.assertTrue(is_same_codec("ascii", "ascii")) + self.assertTrue(is_same_codec("ascii", "ASCII")) + + self.assertTrue(is_same_codec("utf-8", "utf-8")) + self.assertTrue(is_same_codec("utf-8", "utf8")) + self.assertTrue(is_same_codec("utf-8", "UTF_8")) + + self.assertFalse(is_same_codec("ascii", "utf-8")) + +#========================================================= #test des module #========================================================= class DesTest(TestCase): @@ -293,24 +428,30 @@ class MD4_Test(TestCase): vectors = [ # input -> hex digest - ("", "31d6cfe0d16ae931b73c59d7e0c089c0"), - ("a", "bde52cb31de33e46245e05fbdbd6fb24"), - ("abc", "a448017aaf21d8525fc10ae87aa6729d"), - ("message digest", "d9130a8164549fe818874806e1c7014b"), - ("abcdefghijklmnopqrstuvwxyz", "d79e1c308aa5bbcdeea8ed63df412da9"), - ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "043f8582f241db351ce627e153e7f0e4"), - ("12345678901234567890123456789012345678901234567890123456789012345678901234567890", "e33b4ddc9c38f2199c3e7b164fcc0536"), + (b(""), "31d6cfe0d16ae931b73c59d7e0c089c0"), + (b("a"), "bde52cb31de33e46245e05fbdbd6fb24"), + (b("abc"), "a448017aaf21d8525fc10ae87aa6729d"), + (b("message digest"), "d9130a8164549fe818874806e1c7014b"), + (b("abcdefghijklmnopqrstuvwxyz"), "d79e1c308aa5bbcdeea8ed63df412da9"), + (b("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), "043f8582f241db351ce627e153e7f0e4"), + (b("12345678901234567890123456789012345678901234567890123456789012345678901234567890"), "e33b4ddc9c38f2199c3e7b164fcc0536"), ] def test_md4_update(self): "test md4 update" - h = md4('') + h = md4(b('')) self.assertEqual(h.hexdigest(), "31d6cfe0d16ae931b73c59d7e0c089c0") - - h.update('a') + + #NOTE: under py2, hashlib methods try to encode to ascii, + # though shouldn't rely on that. + # Py3k # + #self.assertRaises(TypeError, h.update, u'x') + # end Py3k # + + h.update(b('a')) self.assertEqual(h.hexdigest(), "bde52cb31de33e46245e05fbdbd6fb24") - h.update('bcdefghijklmnopqrstuvwxyz') + h.update(b('bcdefghijklmnopqrstuvwxyz')) self.assertEqual(h.hexdigest(), "d79e1c308aa5bbcdeea8ed63df412da9") def test_md4_hexdigest(self): @@ -323,17 +464,17 @@ class MD4_Test(TestCase): "test md4 digest()" for input, hex in self.vectors: out = md4(input).digest() - self.assertEqual(hexlify(out), hex) + self.assertEqual(to_native_str(hexlify(out)), hex) def test_md4_copy(self): "test md4 copy()" - h = md4('abc') + h = md4(b('abc')) h2 = h.copy() - h2.update('def') + h2.update(b('def')) self.assertEquals(h2.hexdigest(), '804e7f1c2586e50b49ac65db5b645131') - h.update('ghi') + h.update(b('ghi')) self.assertEquals(h.hexdigest(), 'c5225580bfe176f6deeee33dee98732c') #========================================================= @@ -352,7 +493,7 @@ class KdfTest(TestCase): "test pbkdf1" for secret, salt, rounds, klen, hash, correct in [ #http://www.di-mgt.com.au/cryptoKDFs.html - ('password', hb('78578E5A5D63CB06'), 1000, 16, 'sha1', + (b('password'), hb('78578E5A5D63CB06'), 1000, 16, 'sha1', hb('dc19847e05c64d2faf10ebfb4a3d2a20')), ]: result = pbkdf2.pbkdf1(secret, salt, rounds, klen, hash) @@ -392,44 +533,44 @@ class _Pbkdf2BackendTest(TestCase): #test case 1 / 128 bit ( - unhexlify("cdedb5281bb2f801565a1122b2563515"), - "password", "ATHENA.MIT.EDUraeburn", 1, 16 + hb("cdedb5281bb2f801565a1122b2563515"), + b("password"), b("ATHENA.MIT.EDUraeburn"), 1, 16 ), #test case 2 / 128 bit ( - unhexlify("01dbee7f4a9e243e988b62c73cda935d"), - "password", "ATHENA.MIT.EDUraeburn", 2, 16 + hb("01dbee7f4a9e243e988b62c73cda935d"), + b("password"), b("ATHENA.MIT.EDUraeburn"), 2, 16 ), #test case 2 / 256 bit ( - unhexlify("01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86"), - "password", "ATHENA.MIT.EDUraeburn", 2, 32 + hb("01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86"), + b("password"), b("ATHENA.MIT.EDUraeburn"), 2, 32 ), #test case 3 / 256 bit ( - unhexlify("5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13"), - "password", "ATHENA.MIT.EDUraeburn", 1200, 32 + hb("5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13"), + b("password"), b("ATHENA.MIT.EDUraeburn"), 1200, 32 ), #test case 4 / 256 bit ( - unhexlify("d1daa78615f287e6a1c8b120d7062a493f98d203e6be49a6adf4fa574b6e64ee"), - "password", '\x12\x34\x56\x78\x78\x56\x34\x12', 5, 32 + hb("d1daa78615f287e6a1c8b120d7062a493f98d203e6be49a6adf4fa574b6e64ee"), + b("password"), b('\x12\x34\x56\x78\x78\x56\x34\x12'), 5, 32 ), #test case 5 / 256 bit ( - unhexlify("139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1"), - "X"*64, "pass phrase equals block size", 1200, 32 + hb("139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1"), + b("X"*64), b("pass phrase equals block size"), 1200, 32 ), #test case 6 / 256 bit ( - unhexlify("9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a"), - "X"*65, "pass phrase exceeds block size", 1200, 32 + hb("9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a"), + b("X"*65), b("pass phrase exceeds block size"), 1200, 32 ), ]) @@ -438,18 +579,18 @@ class _Pbkdf2BackendTest(TestCase): self.assertFunctionResults(pbkdf2.pbkdf2, [ ( - unhexlify("0c60c80f961f0e71f3a9b524af6012062fe037a6"), - "password", "salt" , 1, 20, + hb("0c60c80f961f0e71f3a9b524af6012062fe037a6"), + b("password"), b("salt"), 1, 20, ), ( - unhexlify("ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"), - "password", "salt", 2, 20, + hb("ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"), + b("password"), b("salt"), 2, 20, ), ( - unhexlify("4b007901b765489abead49d926f721d065a429c1"), - "password", "salt", 4096, 20, + hb("4b007901b765489abead49d926f721d065a429c1"), + b("password"), b("salt"), 4096, 20, ), #just runs too long - could enable if ALL option is set @@ -460,51 +601,51 @@ class _Pbkdf2BackendTest(TestCase): ##), ( - unhexlify("3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"), - "passwordPASSWORDpassword", - "saltSALTsaltSALTsaltSALTsaltSALTsalt", + hb("3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"), + b("passwordPASSWORDpassword"), + b("saltSALTsaltSALTsaltSALTsaltSALTsalt"), 4096, 25, ), ( - unhexlify("56fa6aa75548099dcc37d7f03425e0c3"), - "pass\00word", "sa\00lt", 4096, 16, + hb("56fa6aa75548099dcc37d7f03425e0c3"), + b("pass\00word"), b("sa\00lt"), 4096, 16, ), ]) def test_invalid_values(self): #invalid rounds - self.assertRaises(ValueError, pbkdf2.pbkdf2, 'password', 'salt', -1, 16) - self.assertRaises(ValueError, pbkdf2.pbkdf2, 'password', 'salt', 0, 16) - self.assertRaises(TypeError, pbkdf2.pbkdf2, 'password', 'salt', 'x', 16) + self.assertRaises(ValueError, pbkdf2.pbkdf2, b('password'), b('salt'), -1, 16) + self.assertRaises(ValueError, pbkdf2.pbkdf2, b('password'), b('salt'), 0, 16) + self.assertRaises(TypeError, pbkdf2.pbkdf2, b('password'), b('salt'), 'x', 16) #invalid keylen - self.assertRaises(ValueError, pbkdf2.pbkdf2, 'password', 'salt', 1, 20*(2**32-1)+1) + self.assertRaises(ValueError, pbkdf2.pbkdf2, b('password'), b('salt'), 1, 20*(2**32-1)+1) #invalid salt type - self.assertRaises(TypeError, pbkdf2.pbkdf2, 'password', 5, 1, 10) + self.assertRaises(TypeError, pbkdf2.pbkdf2, b('password'), 5, 1, 10) #invalid secret type - self.assertRaises(TypeError, pbkdf2.pbkdf2, 5, 'salt', 1, 10) + self.assertRaises(TypeError, pbkdf2.pbkdf2, 5, b('salt'), 1, 10) #invalid hash - self.assertRaises(ValueError, pbkdf2.pbkdf2, 'password', 'salt', 1, 16, 'hmac-foo') - self.assertRaises(ValueError, pbkdf2.pbkdf2, 'password', 'salt', 1, 16, 'foo') - self.assertRaises(TypeError, pbkdf2.pbkdf2, 'password', 'salt', 1, 16, 5) + self.assertRaises(ValueError, pbkdf2.pbkdf2, b('password'), b('salt'), 1, 16, 'hmac-foo') + self.assertRaises(ValueError, pbkdf2.pbkdf2, b('password'), b('salt'), 1, 16, 'foo') + self.assertRaises(TypeError, pbkdf2.pbkdf2, b('password'), b('salt'), 1, 16, 5) def test_hmac_sha1(self): "test independant hmac_sha1() method" self.assertEqual( - pbkdf2.hmac_sha1("secret", "salt"), - '\xfc\xd4\x0c;]\r\x97\xc6\xf1S\x8d\x93\xb9\xeb\xc6\x00\x04.\x8b\xfe' + pbkdf2.hmac_sha1(b("secret"), b("salt")), + b('\xfc\xd4\x0c;]\r\x97\xc6\xf1S\x8d\x93\xb9\xeb\xc6\x00\x04.\x8b\xfe') ) - def test_hmac_sha1_string(self): + def test_sha1_string(self): "test various prf values" self.assertEqual( - pbkdf2.pbkdf2(u"secret", u"salt", 10, 16, "hmac-sha1"), - '\xe2H\xfbk\x136QF\xf8\xacc\x07\xcc"(\x12' + pbkdf2.pbkdf2(b("secret"), b("salt"), 10, 16, "hmac-sha1"), + b('\xe2H\xfbk\x136QF\xf8\xacc\x07\xcc"(\x12') ) def test_sha512_string(self): @@ -514,9 +655,9 @@ class _Pbkdf2BackendTest(TestCase): #case taken from example in http://grub.enbug.org/Authentication ( - unhexlify("887CFF169EA8335235D8004242AA7D6187A41E3187DF0CE14E256D85ED97A97357AAA8FF0A3871AB9EEFF458392F462F495487387F685B7472FC6C29E293F0A0"), - "hello", - unhexlify("9290F727ED06C38BA4549EF7DE25CF5642659211B7FC076F2D28FEFD71784BB8D8F6FB244A8CC5C06240631B97008565A120764C0EE9C2CB0073994D79080136"), + hb("887CFF169EA8335235D8004242AA7D6187A41E3187DF0CE14E256D85ED97A97357AAA8FF0A3871AB9EEFF458392F462F495487387F685B7472FC6C29E293F0A0"), + b("hello"), + hb("9290F727ED06C38BA4549EF7DE25CF5642659211B7FC076F2D28FEFD71784BB8D8F6FB244A8CC5C06240631B97008565A120764C0EE9C2CB0073994D79080136"), 10000, 64, "hmac-sha512" ), ]) @@ -531,9 +672,9 @@ class _Pbkdf2BackendTest(TestCase): #case taken from example in http://grub.enbug.org/Authentication ( - unhexlify("887CFF169EA8335235D8004242AA7D6187A41E3187DF0CE14E256D85ED97A97357AAA8FF0A3871AB9EEFF458392F462F495487387F685B7472FC6C29E293F0A0"), - "hello", - unhexlify("9290F727ED06C38BA4549EF7DE25CF5642659211B7FC076F2D28FEFD71784BB8D8F6FB244A8CC5C06240631B97008565A120764C0EE9C2CB0073994D79080136"), + hb("887CFF169EA8335235D8004242AA7D6187A41E3187DF0CE14E256D85ED97A97357AAA8FF0A3871AB9EEFF458392F462F495487387F685B7472FC6C29E293F0A0"), + b("hello"), + hb("9290F727ED06C38BA4549EF7DE25CF5642659211B7FC076F2D28FEFD71784BB8D8F6FB244A8CC5C06240631B97008565A120764C0EE9C2CB0073994D79080136"), 10000, 64, prf, ), ]) diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py index bebf7a8..8064aa0 100644 --- a/passlib/utils/__init__.py +++ b/passlib/utils/__init__.py @@ -38,6 +38,7 @@ __all__ = [ #bytes<->unicode 'to_bytes', 'to_unicode', + 'to_native_str', 'is_same_codec', #byte manipulation @@ -290,6 +291,38 @@ def to_unicode(source, source_encoding="utf-8", errname="value"): raise TypeError("%s must be unicode or %s-encoded bytes, not %s" % (errname, source_encoding, type(source))) +def to_native_str(source, encoding="utf-8", errname="value"): + """take in unicode or bytes, return native string + + python 2: encodes unicode using specified encoding, leaves bytes alone. + python 3: decodes bytes using specified encoding, leaves unicode alone. + + :raises TypeError: if source is not unicode or bytes. + + :arg source: source bytes/unicode to process + :arg encoding: encoding to use when encoding unicode / decoding bytes + :param errname: optional name of variable/noun to reference when raising errors + + :returns: :class:`str` instance + """ + assert encoding + if isinstance(source, bytes): + # Py2k # + return source + # Py3k # + #return source.decode(encoding) + # end Py3k # + + elif isinstance(source, unicode): + # Py2k # + return source.encode(encoding) + # Py3k # + #return source + # end Py3k # + + else: + raise TypeError("%s must be unicode or bytes, not %s" % (errname, type(source))) + #-------------------------------------------------- #support utils #-------------------------------------------------- @@ -321,6 +354,7 @@ BEMPTY = b('') #helpers for joining / extracting elements bjoin = BEMPTY.join +ujoin = u''.join def belem_join(elems): """takes series of bytes elements, returns bytes. @@ -509,7 +543,7 @@ def genseed(value=None): #if value is rng, extract a bunch of bits from it's state if hasattr(value, "getrandbits"): value = value.getrandbits(256) - text = "%s %s %s %.15f %s" % ( + text = u"%s %s %s %.15f %s" % ( value, #if user specified a seed value (eg current rng state), mix it in @@ -523,11 +557,11 @@ def genseed(value=None): time.time(), #the current time, to whatever precision os uses - os.urandom(16) if has_urandom else 0, + os.urandom(16).decode("latin-1") if has_urandom else 0, #if urandom available, might as well mix some bytes in. ) #hash it all up and return it as int - return long(sha256(text).hexdigest(), 16) + return long(sha256(text.encode("utf-8")).hexdigest(), 16) if has_urandom: rng = random.SystemRandom() @@ -548,18 +582,28 @@ def getrandbytes(rng, count): ##if meth: ## return meth(count) - #XXX: break into chunks for large number of bits? if not count: - return '' - value = rng.getrandbits(count<<3) - buf = StringIO() - for i in xrange(count): - buf.write(chr(value & 0xff)) - value //= 0xff - return buf.getvalue() + return BEMPTY + def helper(): + #XXX: break into chunks for large number of bits? + value = rng.getrandbits(count<<3) + i = 0 + while i < count: + # Py2k # + yield chr(value & 0xff) + # Py3k # + #yield value & 0xff + # end Py3k # + value >>= 3 + i += 1 + # Py2k # + return bjoin(helper()) + # Py3k # + #return bytes(helper()) + # end Py3k # def getrandstr(rng, charset, count): - """return character string containg *count* number of chars, whose elements are drawn from specified charset, using specified rng""" + """return string containing *count* number of chars/bytes, whose elements are drawn from specified charset, using specified rng""" #check alphabet & count if count < 0: raise ValueError("count must be >= 0") @@ -570,16 +614,25 @@ def getrandstr(rng, charset, count): return charset * count #get random value, and write out to buffer - #XXX: break into chunks for large number of letters? - value = rng.randrange(0, letters**count) - buf = StringIO() - for i in xrange(count): - buf.write(charset[value % letters]) - value //= letters - assert value == 0 - return buf.getvalue() - -def generate_password(size=10, charset=u'2346789ABCDEFGHJKMNPQRTUVWXYZabcdefghjkmnpqrstuvwxyz'): + def helper(): + #XXX: break into chunks for large number of letters? + value = rng.randrange(0, letters**count) + i = 0 + while i < count: + yield charset[value % letters] + value //= letters + i += 1 + + if isinstance(charset, unicode): + return ujoin(helper()) + else: + # Py2k # + return bjoin(helper()) + # Py3k # + #return bytes(helper()) + # end Py3k # + +def generate_password(size=10, charset='2346789ABCDEFGHJKMNPQRTUVWXYZabcdefghjkmnpqrstuvwxyz'): """generate random password using given length & chars :param size: diff --git a/passlib/utils/md4.py b/passlib/utils/md4.py index d81afe5..40a48e4 100644 --- a/passlib/utils/md4.py +++ b/passlib/utils/md4.py @@ -15,6 +15,7 @@ from binascii import hexlify import struct from warnings import warn #site +from passlib.utils import b, bytes, to_native_str #local __all__ = [ "md4" ] #========================================================================= @@ -71,7 +72,7 @@ class md4(object): def __init__(self, content=None): self._count = 0 self._state = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476] - self._buf = '' + self._buf = b('') if content: self.update(content) @@ -173,6 +174,8 @@ class md4(object): orig[i] = (orig[i]+state[i]) & MASK_32 def update(self, content): + if not isinstance(content, bytes): + raise TypeError("expected bytes") buf = self._buf if buf: content = buf + content @@ -205,7 +208,7 @@ class md4(object): # then last 8 bytes = msg length in bits buf = self._buf msglen = self._count*512 + len(buf)*8 - block = buf + '\x80' + '\x00' * ((119-len(buf)) % 64) + \ + block = buf + b('\x80') + b('\x00') * ((119-len(buf)) % 64) + \ struct.pack("<2I", msglen & MASK_32, (msglen>>32) & MASK_32) if len(block) == 128: self._process(block[:64]) @@ -220,7 +223,8 @@ class md4(object): return out def hexdigest(self): - return hexlify(self.digest()) + return to_native_str(hexlify(self.digest()), "latin-1") + #========================================================================= #eoc #========================================================================= @@ -252,7 +256,7 @@ if _has_native_md4(): #overwrite md4 class w/ hashlib wrapper def md4(content=None): "wrapper for hashlib.new('md4')" - return hashlib.new('md4', content or '') + return hashlib.new('md4', content or b('')) else: del hashlib diff --git a/passlib/utils/pbkdf2.py b/passlib/utils/pbkdf2.py index adb071f..7bc6935 100644 --- a/passlib/utils/pbkdf2.py +++ b/passlib/utils/pbkdf2.py @@ -8,7 +8,6 @@ maybe rename to "kdf" since it's getting more key derivation functions added. #================================================================================= #core from binascii import unhexlify -from cStringIO import StringIO import hashlib import hmac import logging; log = logging.getLogger(__name__) @@ -21,7 +20,7 @@ try: except ImportError: _EVP = None #pkg -from passlib.utils import xor_bytes +from passlib.utils import xor_bytes, to_bytes, native_str, b, bytes #local __all__ = [ "hmac_sha1", @@ -30,6 +29,12 @@ __all__ = [ "pbkdf2", ] +# Py2k # +from cStringIO import StringIO as BytesIO +# Py3k # +#from io import BytesIO +# end Py3k # + #================================================================================= #quick hmac_sha1 implementation used various places #================================================================================= @@ -40,12 +45,12 @@ def hmac_sha1(key, msg): if _EVP: #default *should* be sha1, which saves us a wrapper function, but might as well check. try: - result = _EVP.hmac('x','y') + result = _EVP.hmac(b('x'),b('y')) except ValueError: #pragma: no cover #this is probably not a good sign if it happens. warn("PassLib: M2Crypt.EVP.hmac() unexpected threw value error during passlib startup test") else: - if result == ',\x1cb\xe0H\xa5\x82M\xfb>\xd6\x98\xef\x8e\xf9oQ\x85\xa3i': + if result == b(',\x1cb\xe0H\xa5\x82M\xfb>\xd6\x98\xef\x8e\xf9oQ\x85\xa3i'): hmac_sha1 = _EVP.hmac #================================================================================= @@ -56,7 +61,7 @@ def _get_hmac_prf(digest): #check if m2crypto is present and supports requested digest if _EVP: try: - result = _EVP.hmac('x', 'y', digest) + result = _EVP.hmac(b('x'), b('y'), digest) except ValueError: pass else: @@ -132,14 +137,14 @@ def get_prf(name): global _prf_cache if name in _prf_cache: return _prf_cache[name] - if isinstance(name, str): + if isinstance(name, native_str): if name.startswith("hmac-") or name.startswith("hmac_"): retval = _get_hmac_prf(name[5:]) else: raise ValueError("unknown prf algorithm: %r" % (name,)) elif callable(name): #assume it's a callable, use it directly - digest_size = len(name('x','y')) + digest_size = len(name(b('x'),b('y'))) retval = (name, digest_size) else: raise TypeError("prf must be string or callable") @@ -175,19 +180,11 @@ def pbkdf1(secret, salt, rounds, keylen, hash="sha1", encoding="utf8"): than the digest size of the specified hash. """ - #prepare secret - if isinstance(secret, unicode): - secret = secret.encode(encoding) - elif not isinstance(secret, str): - raise TypeError("secret must be str or unicode") - - #prepare salt - if isinstance(salt, unicode): - salt = salt.encode(encoding) - elif not isinstance(salt, str): - raise TypeError("salt must be str or unicode") + #prepare secret & salt + secret = to_bytes(secret, encoding, errname="secret") + salt = to_bytes(salt, encoding, errname="salt") - #preprare rounds + #prepare rounds if not isinstance(rounds, (int, long)): raise TypeError("rounds must be an integer") if rounds < 1: @@ -198,7 +195,7 @@ def pbkdf1(secret, salt, rounds, keylen, hash="sha1", encoding="utf8"): raise ValueError("keylen must be at least 0") #resolve hash - if isinstance(hash, str): + if isinstance(hash, native_str): #check for builtin hash hf = getattr(hashlib, hash, None) if hf is None: @@ -244,20 +241,11 @@ def pbkdf2(secret, salt, rounds, keylen, prf="hmac-sha1", encoding="utf8"): :returns: raw bytes of generated key """ + #prepare secret & salt + secret = to_bytes(secret, encoding, errname="secret") + salt = to_bytes(salt, encoding, errname="salt") - #prepare secret - if isinstance(secret, unicode): - secret = secret.encode(encoding) - elif not isinstance(secret, str): - raise TypeError("secret must be str or unicode") - - #prepare salt - if isinstance(salt, unicode): - salt = salt.encode(encoding) - elif not isinstance(salt, str): - raise TypeError("salt must be str or unicode") - - #preprare rounds + #prepare rounds if not isinstance(rounds, (int, long)): raise TypeError("rounds must be an integer") if rounds < 1: @@ -284,7 +272,7 @@ def pbkdf2(secret, salt, rounds, keylen, prf="hmac-sha1", encoding="utf8"): raise ValueError("key length to long") #build up key from blocks - out = StringIO() + out = BytesIO() write = out.write for i in xrange(1,bcount+1): block = tmp = encode_block(secret, salt + pack(">L", i)) |
