Tests now use a dummy hash function that does nothing (basically) and run a bit faster now.pull/101/head
parent
91e63c7e5f
commit
9260e143f1
@ -0,0 +1,72 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
try:
|
||||
import unittest2 as unittest
|
||||
except ImportError:
|
||||
import unittest
|
||||
|
||||
from isso.compat import PY2K, string_types
|
||||
from isso.core import Config
|
||||
from isso.utils.hash import Hash, PBKDF2, new
|
||||
|
||||
|
||||
class TestHasher(unittest.TestCase):
|
||||
|
||||
def test_hash(self):
|
||||
self.assertRaises(TypeError, Hash, "Foo")
|
||||
|
||||
self.assertEqual(Hash(b"").salt, b"")
|
||||
self.assertEqual(Hash().salt, Hash.salt)
|
||||
|
||||
h = Hash(b"", func=None)
|
||||
|
||||
self.assertRaises(TypeError, h.hash, "...")
|
||||
self.assertEqual(h.hash(b"..."), b"...")
|
||||
self.assertIsInstance(h.uhash(u"..."), string_types)
|
||||
|
||||
@unittest.skipIf(PY2K, "byte/str quirks")
|
||||
def test_uhash(self):
|
||||
h = Hash(b"", func=None)
|
||||
self.assertRaises(TypeError, h.uhash, b"...")
|
||||
|
||||
|
||||
class TestPBKDF2(unittest.TestCase):
|
||||
|
||||
def test_default(self):
|
||||
pbkdf2 = PBKDF2(iterations=1000) # original setting (and still default)
|
||||
self.assertEqual(pbkdf2.uhash(""), "42476aafe2e4")
|
||||
|
||||
def test_different_salt(self):
|
||||
a = PBKDF2(b"a", iterations=1)
|
||||
b = PBKDF2(b"b", iterations=1)
|
||||
self.assertNotEqual(a.hash(b""), b.hash(b""))
|
||||
|
||||
|
||||
class TestCreate(unittest.TestCase):
|
||||
|
||||
def test_default(self):
|
||||
conf = Config.load(None)
|
||||
self.assertIsInstance(new(conf.section("hash")), PBKDF2)
|
||||
|
||||
def test_custom(self):
|
||||
|
||||
def _new(val):
|
||||
conf = Config.load(None)
|
||||
conf.set("hash", "algorithm", val)
|
||||
return new(conf.section("hash"))
|
||||
|
||||
sha1 = _new("sha1")
|
||||
self.assertIsInstance(sha1, Hash)
|
||||
self.assertEqual(sha1.func, "sha1")
|
||||
self.assertRaises(ValueError, _new, "foo")
|
||||
|
||||
pbkdf2 = _new("pbkdf2:16")
|
||||
self.assertIsInstance(pbkdf2, PBKDF2)
|
||||
self.assertEqual(pbkdf2.iterations, 16)
|
||||
|
||||
pbkdf2 = _new("pbkdf2:16:2:md5")
|
||||
self.assertIsInstance(pbkdf2, PBKDF2)
|
||||
self.assertEqual(pbkdf2.dklen, 2)
|
||||
self.assertEqual(pbkdf2.func, "md5")
|
@ -0,0 +1,112 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import codecs
|
||||
import hashlib
|
||||
|
||||
from isso.compat import string_types, text_type as str
|
||||
|
||||
try:
|
||||
from werkzeug.security import pbkdf2_bin as pbkdf2
|
||||
except ImportError:
|
||||
try:
|
||||
from passlib.utils.pbkdf2 import pbkdf2 as _pbkdf2
|
||||
|
||||
def pbkdf2(val, salt, iterations, dklen, func):
|
||||
return _pbkdf2(val, salt, iterations, dklen, ("hmac-" + func).encode("utf-8"))
|
||||
except ImportError as ex:
|
||||
raise ImportError("No PBKDF2 implementation found. Either upgrade " +
|
||||
"to `werkzeug` 0.9 or install `passlib`.")
|
||||
|
||||
|
||||
def _TypeError(name, expected, val):
|
||||
return TypeError("'{0}' must be {1}, not {2}".format(
|
||||
name, expected, val.__class__.__name__))
|
||||
|
||||
|
||||
class Hash(object):
|
||||
|
||||
func = None
|
||||
salt = b"Eech7co8Ohloopo9Ol6baimi"
|
||||
|
||||
def __init__(self, salt=None, func="sha1"):
|
||||
|
||||
if func is not None:
|
||||
hashlib.new(func) # may not be available
|
||||
self.func = func
|
||||
|
||||
if salt is not None:
|
||||
if not isinstance(salt, bytes):
|
||||
raise _TypeError("salt", "bytes", salt)
|
||||
self.salt = salt
|
||||
|
||||
def hash(self, val):
|
||||
"""Calculate hash from value (must be bytes)."""
|
||||
|
||||
if not isinstance(val, bytes):
|
||||
raise _TypeError("val", "bytes", val)
|
||||
|
||||
rv = self.compute(val)
|
||||
|
||||
if not isinstance(val, bytes):
|
||||
raise _TypeError("val", "bytes", rv)
|
||||
|
||||
return rv
|
||||
|
||||
def uhash(self, val):
|
||||
"""Calculate hash from unicode value and return hex value as unicode"""
|
||||
|
||||
if not isinstance(val, string_types):
|
||||
raise _TypeError("val", "str", val)
|
||||
|
||||
return codecs.encode(self.hash(val.encode("utf-8")), "hex_codec").decode("utf-8")
|
||||
|
||||
def compute(self, val):
|
||||
if self.func is None:
|
||||
return val
|
||||
|
||||
h = hashlib.new(self.func)
|
||||
h.update(val)
|
||||
|
||||
return h.digest()
|
||||
|
||||
|
||||
class PBKDF2(Hash):
|
||||
|
||||
def __init__(self, salt=None, iterations=1000, dklen=6, func="sha1"):
|
||||
super(PBKDF2, self).__init__(salt)
|
||||
|
||||
self.iterations = iterations
|
||||
self.dklen = dklen
|
||||
self.func = func
|
||||
|
||||
def compute(self, val):
|
||||
return pbkdf2(val, self.salt, self.iterations, self.dklen, self.func)
|
||||
|
||||
|
||||
def new(conf):
|
||||
"""Factory to create hash functions from configuration section. If an
|
||||
algorithm takes custom parameters, you can separate them by a colon like
|
||||
this: pbkdf2:arg1:arg2:arg3."""
|
||||
|
||||
algorithm = conf.get("algorithm")
|
||||
salt = conf.get("salt").encode("utf-8")
|
||||
|
||||
if algorithm == "none":
|
||||
return Hash(salt, None)
|
||||
elif algorithm.startswith("pbkdf2"):
|
||||
kwargs = {}
|
||||
tail = algorithm.partition(":")[2]
|
||||
for func, key in ((int, "iterations"), (int, "dklen"), (str, "func")):
|
||||
head, _, tail = tail.partition(":")
|
||||
if not head:
|
||||
break
|
||||
kwargs[key] = func(head)
|
||||
|
||||
return PBKDF2(salt, **kwargs)
|
||||
else:
|
||||
return Hash(salt, algorithm)
|
||||
|
||||
|
||||
sha1 = Hash(func="sha1").uhash
|
Loading…
Reference in new issue