summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2012-04-28 11:37:41 -0400
committerEli Collins <elic@assurancetechnologies.com>2012-04-28 11:37:41 -0400
commit4df62c3081c19709b8303565d0bd0ac39aa7f91a (patch)
tree08f2246366dada72b6bfbe4aab3e602a31bf4541
parent2f85f16f82c183eda23f361a0f3275cbcd7383d6 (diff)
downloadpasslib-4df62c3081c19709b8303565d0bd0ac39aa7f91a.tar.gz
added some missing tests; fixed a bunch of bugs
-rw-r--r--passlib/context.py7
-rw-r--r--passlib/ext/django/models.py2
-rw-r--r--passlib/ext/django/utils.py10
-rw-r--r--passlib/handlers/sha1_crypt.py6
-rw-r--r--passlib/ifc.py6
-rw-r--r--passlib/registry.py2
-rw-r--r--passlib/tests/test_context.py53
-rw-r--r--passlib/tests/test_ext_django.py51
-rw-r--r--passlib/tests/test_handlers.py20
-rw-r--r--passlib/tests/test_hosts.py3
-rw-r--r--passlib/tests/test_registry.py4
-rw-r--r--passlib/tests/test_utils_crypto.py6
-rw-r--r--passlib/tests/utils.py9
-rw-r--r--passlib/utils/_blowfish/base.py1
-rw-r--r--passlib/utils/compat.py8
-rw-r--r--passlib/utils/md4.py3
-rw-r--r--passlib/utils/pbkdf2.py47
17 files changed, 167 insertions, 71 deletions
diff --git a/passlib/context.py b/passlib/context.py
index e64ffc7..8fb8006 100644
--- a/passlib/context.py
+++ b/passlib/context.py
@@ -1495,7 +1495,12 @@ class CryptContext(object):
for scheme in value:
if not isinstance(scheme, str):
raise ExpectedTypeError(value, "str", "deprecated element")
- if scheme not in schemes and scheme != "auto":
+ if scheme in schemes:
+ continue
+ elif scheme == "auto":
+ if len(value) > 1:
+ raise ValueError("cannot list other schemes if ``deprecated=['auto']`` is used")
+ else:
raise KeyError("deprecated scheme not found "
"in policy: %r" % (scheme,))
# TODO: make sure there's at least one non-deprecated scheme.
diff --git a/passlib/ext/django/models.py b/passlib/ext/django/models.py
index 77d1443..eb82143 100644
--- a/passlib/ext/django/models.py
+++ b/passlib/ext/django/models.py
@@ -75,7 +75,7 @@ def _apply_patch():
return (encoded is not None and encoded != UNUSABLE_PASSWORD)
def is_valid_secret(secret):
- return password is not None
+ return secret is not None
else:
has_hashers = True
diff --git a/passlib/ext/django/utils.py b/passlib/ext/django/utils.py
index 4358ab3..72e50ae 100644
--- a/passlib/ext/django/utils.py
+++ b/passlib/ext/django/utils.py
@@ -18,7 +18,7 @@ from passlib.context import CryptContext
from passlib.exc import PasslibRuntimeWarning
from passlib.registry import get_crypt_handler, list_crypt_handlers
from passlib.utils import classproperty
-from passlib.utils.compat import bytes, method_function_attr, iteritems
+from passlib.utils.compat import bytes, get_method_function, iteritems
# local
__all__ = [
"get_preset_config",
@@ -205,7 +205,7 @@ def get_passlib_hasher(handler):
if hasattr(handler, "django_name"):
# return native hasher instance
# XXX: should cache this too.
- return _get_hasher(handler.django_name)()
+ return _get_hasher(handler.django_name)
if handler.name == "django_disabled":
raise ValueError("can't wrap unusable-password handler")
try:
@@ -359,11 +359,7 @@ class _PatchManager(object):
@staticmethod
def _is_same_value(left, right):
"check if two values are the same (stripping method wrappers, etc)"
- def resolve(value):
- if hasattr(value, method_function_attr):
- return getattr(value, method_function_attr)
- return value
- return resolve(left) == resolve(right)
+ return get_method_function(left) == get_method_function(right)
#===================================================================
# reading
diff --git a/passlib/handlers/sha1_crypt.py b/passlib/handlers/sha1_crypt.py
index 89145a7..852b493 100644
--- a/passlib/handlers/sha1_crypt.py
+++ b/passlib/handlers/sha1_crypt.py
@@ -15,7 +15,7 @@ from warnings import warn
#libs
from passlib.utils import classproperty, h64, safe_crypt, test_crypt
from passlib.utils.compat import b, bytes, u, uascii_to_str, unicode
-from passlib.utils.pbkdf2 import hmac_sha1
+from passlib.utils.pbkdf2 import get_prf
import passlib.utils.handlers as uh
#pkg
#local
@@ -24,6 +24,8 @@ __all__ = [
#=========================================================
#sha1-crypt
#=========================================================
+_hmac_sha1 = get_prf("hmac-sha1")[0]
+
class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
"""This class implements the SHA1-Crypt password hash, and follows the :ref:`password-hash-api`.
@@ -104,7 +106,7 @@ class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
result = result.encode("ascii")
r = 0
while r < rounds:
- result = hmac_sha1(secret, result)
+ result = _hmac_sha1(secret, result)
r += 1
return h64.encode_transposed_bytes(result, self._chk_offsets).decode("ascii")
diff --git a/passlib/ifc.py b/passlib/ifc.py
index 91f5feb..8d9d49f 100644
--- a/passlib/ifc.py
+++ b/passlib/ifc.py
@@ -52,9 +52,9 @@ class PasswordHash(object):
#----------------------------------
# general information
#----------------------------------
- name = abstractproperty()
- setting_kwds = abstractproperty()
- context_kwds = abstractproperty()
+ ##name
+ ##setting_kwds
+ ##context_kwds
#----------------------------------
# salt information -- if 'salt' in setting_kwds
diff --git a/passlib/registry.py b/passlib/registry.py
index 1eab7f3..68109cc 100644
--- a/passlib/registry.py
+++ b/passlib/registry.py
@@ -220,7 +220,7 @@ def register_crypt_handler_path(name, path):
if ':' in path:
if path.count(':') > 1:
raise ValueError("path cannot have more than one ':'")
- if path.index('.', path.index(':')) > -1:
+ if path.find('.', path.index(':')) > -1:
raise ValueError("path cannot have '.' to right of ':'")
# store location
diff --git a/passlib/tests/test_context.py b/passlib/tests/test_context.py
index 598457d..b80434c 100644
--- a/passlib/tests/test_context.py
+++ b/passlib/tests/test_context.py
@@ -25,7 +25,7 @@ from passlib import hash
from passlib.context import CryptContext, LazyCryptContext
from passlib.exc import PasslibConfigWarning
from passlib.utils import tick, to_bytes, to_unicode
-from passlib.utils.compat import irange, u
+from passlib.utils.compat import irange, u, unicode, str_to_uascii
import passlib.utils.handlers as uh
from passlib.tests.utils import TestCase, mktemp, catch_warnings, \
gae_env, set_file
@@ -641,7 +641,11 @@ sha512_crypt__min_rounds = 45000
# name not in schemes
self.assertRaises(KeyError, ctx.handler, "mysql323")
- # TODO: per-category
+ # check handler() honors category default
+ ctx = CryptContext("sha256_crypt,md5_crypt", admin__context__default="md5_crypt")
+ self.assertEqual(ctx.handler(), hash.sha256_crypt)
+ self.assertEqual(ctx.handler(category="staff"), hash.sha256_crypt)
+ self.assertEqual(ctx.handler(category="admin"), hash.md5_crypt)
def test_33_options(self):
"test internal _get_record_options() method"
@@ -957,7 +961,50 @@ sha512_crypt__min_rounds = 45000
self.assertFalse(cc.needs_update('$5$rounds=3000$fS9iazEwTKi7QPW4$VasgBC8FqlOvD7x2HhABaMXCTh9jwHclPA9j5YQdns.'))
self.assertTrue(cc.needs_update('$5$rounds=3001$QlFHHifXvpFX4PLs$/0ekt7lSs/lOikSerQ0M/1porEHxYq7W/2hdFpxA3fA'))
- # TODO: check with secret passed in, that _bind_needs_update() is invoked correctly.
+ #--------------------------------------------------------------
+ # test _bind_needs_update() framework
+ #--------------------------------------------------------------
+ bind_state = []
+ check_state = []
+ class dummy(uh.StaticHandler):
+ name = 'dummy'
+ _hash_prefix = '@'
+
+ @classmethod
+ def _bind_needs_update(cls, **settings):
+ bind_state.append(settings)
+ return cls._needs_update
+
+ @classmethod
+ def _needs_update(cls, hash, secret):
+ check_state.append((hash,secret))
+ return secret == "nu"
+
+ def _calc_checksum(self, secret):
+ from hashlib import md5
+ if isinstance(secret, unicode):
+ secret = secret.encode("utf-8")
+ return str_to_uascii(md5(secret).hexdigest())
+
+ # creating context should call bind function w/ settings
+ ctx = CryptContext([dummy])
+ self.assertEqual(bind_state, [{}])
+
+ # calling needs_update should query callback
+ hash = dummy.encrypt("test")
+ self.assertFalse(ctx.needs_update(hash))
+ self.assertEqual(check_state, [(hash,None)])
+ del check_state[:]
+
+ # now with a password
+ self.assertFalse(ctx.needs_update(hash, secret='bob'))
+ self.assertEqual(check_state, [(hash,'bob')])
+ del check_state[:]
+
+ # now when it returns True
+ self.assertTrue(ctx.needs_update(hash, secret='nu'))
+ self.assertEqual(check_state, [(hash,'nu')])
+ del check_state[:]
#--------------------------------------------------------------
# border cases
diff --git a/passlib/tests/test_ext_django.py b/passlib/tests/test_ext_django.py
index 96b7c13..998fa63 100644
--- a/passlib/tests/test_ext_django.py
+++ b/passlib/tests/test_ext_django.py
@@ -11,7 +11,8 @@ import warnings
# pkg
from passlib.apps import django10_context, django14_context
from passlib.context import CryptContext
-from passlib.utils.compat import iteritems, unicode, method_function_attr
+import passlib.exc as exc
+from passlib.utils.compat import iteritems, unicode, get_method_function, u, PY3
from passlib.utils import memoized_property
from passlib.registry import get_crypt_handler
# tests
@@ -22,9 +23,14 @@ from passlib.tests.test_handlers import get_handler_case
#=========================================================
# configure django settings for testcases
#=========================================================
+from passlib.ext.django.utils import DJANGO_VERSION
+
+# disable all Django integration tests under py3,
+# since Django doesn't support py3 yet.
+if PY3 and DJANGO_VERSION < (1,5):
+ DJANGO_VERSION = ()
# convert django version to some cheap flags
-from passlib.ext.django.utils import DJANGO_VERSION
has_django = bool(DJANGO_VERSION)
has_django0 = has_django and DJANGO_VERSION < (1,0)
has_django1 = DJANGO_VERSION >= (1,0)
@@ -110,7 +116,7 @@ def iter_patch_candidates():
if attr.startswith("_"):
continue
value = getattr(obj, attr)
- value = getattr(value, method_function_attr, value)
+ value = get_method_function(value)
source = getattr(value, "__module__", None)
if source:
yield obj, attr, source
@@ -129,6 +135,9 @@ def create_mock_setter():
setter.popstate = popstate
return setter
+def has_backend(handler):
+ return not hasattr(handler, "has_backend") or handler.has_backend()
+
#=========================================================
# sample config used by basic tests
#=========================================================
@@ -500,9 +509,9 @@ class DjangoExtensionTest(TestCase, _ExtensionSupport):
"v2RWkZQzctPdejyRqmmTDQpZN6wTh7.RUy9zF2LftT6")
self.assertEqual(hasher.safe_summary(encoded),
{'algorithm': 'sha256_crypt',
- 'salt': u'abcdab**********',
+ 'salt': u('abcdab**********'),
'iterations': 1234,
- 'hash': u'v2RWkZ*************************************',
+ 'hash': u('v2RWkZ*************************************'),
})
#=========================================================
@@ -580,10 +589,13 @@ class DjangoExtensionTest(TestCase, _ExtensionSupport):
user = FakeUser()
user.set_password("stub")
default = context.handler()
- self.assertTrue(default.verify("stub", user.password))
+ if has_backend(default):
+ self.assertTrue(default.verify("stub", user.password))
+ else:
+ self.assertRaises(exc.MissingBackendError, default.verify, 'stub', user.password)
# test module-level make_password
- if has_django14:
+ if has_backend(default) and has_django14:
hash = hashers.make_password('stub')
self.assertTrue(default.verify('stub', hash))
@@ -591,10 +603,17 @@ class DjangoExtensionTest(TestCase, _ExtensionSupport):
for scheme in context.schemes():
deprecated = context._is_deprecated_scheme(scheme)
assert not (deprecated and scheme == default.name)
- testcase = get_handler_case(scheme)
+ try:
+ testcase = get_handler_case(scheme)
+ except exc.MissingBackendError:
+ assert scheme == "bcrypt"
+ continue
if testcase.is_disabled_handler:
continue
handler = testcase.handler
+ if not has_backend(handler):
+ assert scheme == "django_bcrypt"
+ continue
for secret, hash in testcase.iter_known_hashes():
## print [scheme, secret, hash, deprecated, scheme==default.name]
other = 'stub'
@@ -621,7 +640,8 @@ class DjangoExtensionTest(TestCase, _ExtensionSupport):
self.assertTrue(user.check_password(secret))
# check if it upgraded the hash
- if deprecated:
+ needs_update = context.needs_update(hash)
+ if needs_update:
self.assertNotEqual(user.password, hash)
self.assertFalse(handler.identify(user.password))
self.assertTrue(default.identify(user.password))
@@ -629,10 +649,15 @@ class DjangoExtensionTest(TestCase, _ExtensionSupport):
self.assertEqual(user.password, hash)
# test module-level check_password
- self.assertTrue(check_password(secret, hash, setter=setter))
- self.assertEqual(setter.popstate(), [secret] if deprecated else [])
- self.assertFalse(check_password(other, hash, setter=setter))
- self.assertEqual(setter.popstate(), [])
+ if has_django14 or patched:
+ self.assertTrue(check_password(secret, hash, setter=setter))
+ self.assertEqual(setter.popstate(), [secret] if needs_update else [])
+ self.assertFalse(check_password(other, hash, setter=setter))
+ self.assertEqual(setter.popstate(), [])
+ elif scheme != "hex_md5":
+ # django 1.3 never called check_password() for hex_md5
+ self.assertTrue(check_password(secret, hash))
+ self.assertFalse(check_password(other, hash))
# test module-level identify_hasher
if has_django14 and patched:
diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py
index 206a089..6eca889 100644
--- a/passlib/tests/test_handlers.py
+++ b/passlib/tests/test_handlers.py
@@ -11,7 +11,7 @@ import warnings
#pkg
from passlib import hash
from passlib.utils import repeat_string
-from passlib.utils.compat import irange, PY3, u
+from passlib.utils.compat import irange, PY3, u, get_method_function
from passlib.tests.utils import TestCase, HandlerCase, create_backend_case, \
enable_option, b, catch_warnings, UserHandlerMixin, randintgauss
#module
@@ -70,6 +70,7 @@ class _bcrypt_test(HandlerCase):
"base for BCrypt test cases"
handler = hash.bcrypt
secret_size = 72
+ reduce_default_rounds = True
known_correct_hashes = [
#
@@ -826,6 +827,19 @@ class django_salted_md5_test(HandlerCase, _DjangoHelper):
'md5$aa$bb',
]
+ def get_fuzz_salt_size(self):
+ # workaround for django14 regression --
+ # 1.4 won't accept hashes with empty salt strings, unlike 1.3 and earlier.
+ # looks to be fixed in a future release -- https://code.djangoproject.com/ticket/18144
+ # for now, we avoid salt_size==0 under 1.4
+ handler = self.handler
+ from passlib.tests.test_ext_django import has_django14
+ default = handler.default_salt_size
+ assert handler.min_salt_size == 0
+ lower = 1 if has_django14 else 0
+ upper = handler.max_salt_size or default*4
+ return randintgauss(lower, upper, default, default*.5)
+
class django_salted_sha1_test(HandlerCase, _DjangoHelper):
"test django_salted_sha1"
handler = hash.django_salted_sha1
@@ -854,6 +868,8 @@ class django_salted_sha1_test(HandlerCase, _DjangoHelper):
'sha1$c2e86$0f75',
]
+ get_fuzz_salt_size = get_method_function(django_salted_md5_test.get_fuzz_salt_size)
+
class django_pbkdf2_sha256_test(HandlerCase, _DjangoHelper):
"test django_pbkdf2_sha256"
handler = hash.django_pbkdf2_sha256
@@ -1022,7 +1038,7 @@ class hex_md4_test(HandlerCase):
(UPASS_TABLE, '876078368c47817ce5f9115f3a42cf74'),
]
-class hex_md5_test(HandlerCase, _DjangoHelper):
+class hex_md5_test(HandlerCase):
handler = hash.hex_md5
known_correct_hashes = [
("password", '5f4dcc3b5aa765d61d8327deb882cf99'),
diff --git a/passlib/tests/test_hosts.py b/passlib/tests/test_hosts.py
index a64fb30..69408da 100644
--- a/passlib/tests/test_hosts.py
+++ b/passlib/tests/test_hosts.py
@@ -5,6 +5,7 @@
from __future__ import with_statement
#core
import logging; log = logging.getLogger(__name__)
+import warnings
#site
#pkg
from passlib import hosts, hash as hashmod
@@ -48,6 +49,8 @@ class HostsTest(TestCase):
self.check_unix_disabled(ctx)
def test_bsd_contexts(self):
+ warnings.filterwarnings("ignore",
+ "SECURITY WARNING: .*pure-python bcrypt.*")
for ctx in [
hosts.freebsd_context,
hosts.openbsd_context,
diff --git a/passlib/tests/test_registry.py b/passlib/tests/test_registry.py
index ce9466d..c076845 100644
--- a/passlib/tests/test_registry.py
+++ b/passlib/tests/test_registry.py
@@ -82,7 +82,7 @@ class RegistryTest(TestCase):
def test_register_crypt_handler_path(self):
"test register_crypt_handler_path()"
#NOTE: this messes w/ internals of registry, shouldn't be used publically.
- paths = registry._handler_locations
+ paths = registry._locations
#check namespace is clear
self.assertTrue('dummy_0' not in paths)
@@ -156,7 +156,7 @@ class RegistryTest(TestCase):
name = "dummy_1"
self.assertRaises(KeyError, get_crypt_handler, "dummy_1")
- self.asssertIs(get_crypt_handler("dummy_1", None), None)
+ self.assertIs(get_crypt_handler("dummy_1", None), None)
register_crypt_handler(dummy_1)
self.assertIs(get_crypt_handler("dummy_1"), dummy_1)
diff --git a/passlib/tests/test_utils_crypto.py b/passlib/tests/test_utils_crypto.py
index 94c20e8..5b50d41 100644
--- a/passlib/tests/test_utils_crypto.py
+++ b/passlib/tests/test_utils_crypto.py
@@ -285,7 +285,6 @@ import hashlib
import hmac
from passlib.utils import pbkdf2
-#TODO: should we bother testing hmac_sha1() function? it's verified via sha1_crypt testing.
class CryptoTest(TestCase):
"test various crypto functions"
@@ -490,8 +489,11 @@ class _Pbkdf2BackendTest(TestCase):
def test_hmac_sha1(self):
"test independant hmac_sha1() method"
+ # TODO: expand this into a full test of get_prf()
+ hmac_sha1, size = pbkdf2.get_prf("hmac-sha1")
+ self.assertEqual(size, 20)
self.assertEqual(
- pbkdf2.hmac_sha1(b("secret"), b("salt")),
+ hmac_sha1(b("secret"), b("salt")),
b('\xfc\xd4\x0c;]\r\x97\xc6\xf1S\x8d\x93\xb9\xeb\xc6\x00\x04.\x8b\xfe')
)
diff --git a/passlib/tests/utils.py b/passlib/tests/utils.py
index 35a7b50..77e8933 100644
--- a/passlib/tests/utils.py
+++ b/passlib/tests/utils.py
@@ -1739,8 +1739,13 @@ class HandlerCase(TestCase):
def mangle_fuzz_password(self, secret):
"mangle fuzz-testing password so it doesn't match"
- secret = secret.strip()[1:]
- return secret or self.get_fuzz_password()
+ other = secret.strip()[1:]
+ if other:
+ return other
+ while True:
+ other = self.get_fuzz_password()
+ if other != secret:
+ return other
def get_fuzz_password_pair(self):
"generate random password, and non-matching alternate password"
diff --git a/passlib/utils/_blowfish/base.py b/passlib/utils/_blowfish/base.py
index fdbead1..e725e7f 100644
--- a/passlib/utils/_blowfish/base.py
+++ b/passlib/utils/_blowfish/base.py
@@ -5,6 +5,7 @@
#core
import struct
#pkg
+from passlib.utils.compat import bytes
from passlib.utils import repeat_string
#local
__all__ = [
diff --git a/passlib/utils/compat.py b/passlib/utils/compat.py
index 4f75c4a..71f41a7 100644
--- a/passlib/utils/compat.py
+++ b/passlib/utils/compat.py
@@ -266,12 +266,12 @@ def exc_err():
if PY3:
method_function_attr = "__func__"
- def get_method_function(method):
- return method.__func__
else:
method_function_attr = "im_func"
- def get_method_function(method):
- return method.im_func
+
+def get_method_function(func):
+ "given (potential) method, return underlying function"
+ return getattr(func, method_function_attr, func)
#=============================================================================
# input/output
diff --git a/passlib/utils/md4.py b/passlib/utils/md4.py
index 4389a09..2dd6da9 100644
--- a/passlib/utils/md4.py
+++ b/passlib/utils/md4.py
@@ -248,8 +248,7 @@ def _has_native_md4():
if result == '31d6cfe0d16ae931b73c59d7e0c089c0':
return True
if PYPY and result == '':
- # as of pypy 1.5-1.7, this returns empty string!
- # since this is expected, don't bother w/ warning.
+ # workaround for https://bugs.pypy.org/issue957, fixed in PyPy 1.8
return False
#anything else should alert user
from passlib.exc import PasslibRuntimeWarning
diff --git a/passlib/utils/pbkdf2.py b/passlib/utils/pbkdf2.py
index 9c7be9e..be77e39 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
import hashlib
-from hmac import trans_36, trans_5C
import logging; log = logging.getLogger(__name__)
import re
from struct import pack
@@ -20,11 +19,10 @@ except ImportError:
_EVP = None
#pkg
from passlib.exc import PasslibRuntimeWarning
-from passlib.utils import join_bytes, to_native_str, bytes_to_int, int_to_bytes
+from passlib.utils import join_bytes, to_native_str, bytes_to_int, int_to_bytes, join_byte_values
from passlib.utils.compat import b, bytes, BytesIO, irange, callable, int_types
#local
__all__ = [
- "hmac_sha1",
"get_prf",
"pbkdf1",
"pbkdf2",
@@ -133,29 +131,13 @@ def norm_hash_name(name, format="hashlib"):
return row[idx]
#=================================================================================
-#quick hmac_sha1 implementation used various places
-#=================================================================================
-def hmac_sha1(key, msg):
- "perform raw hmac-sha1 of a message"
- return hmac.new(key, msg, hashlib.sha1).digest()
-
-if _EVP:
- #default *should* be sha1, which saves us a wrapper function, but might as well check.
- try:
- result = _EVP.hmac(b('x'),b('y'))
- except ValueError: #pragma: no cover
- #this is probably not a good sign if it happens.
- from passlib.exc import PasslibRuntimeWarning
- warn("Passlib: M2Crypt.EVP.hmac() unexpected threw value error during "
- "passlib startup test", PasslibRuntimeWarning)
- else:
- if result == b(',\x1cb\xe0H\xa5\x82M\xfb>\xd6\x98\xef\x8e\xf9oQ\x85\xa3i'):
- hmac_sha1 = _EVP.hmac
-
-#=================================================================================
#general prf lookup
#=================================================================================
_BNULL = b('\x00')
+_XY_DIGEST = b(',\x1cb\xe0H\xa5\x82M\xfb>\xd6\x98\xef\x8e\xf9oQ\x85\xa3i')
+
+_trans_5C = join_byte_values((x ^ 0x5C) for x in irange(256))
+_trans_36 = join_byte_values((x ^ 0x36) for x in irange(256))
def _get_hmac_prf(digest):
"helper to return HMAC prf for specific digest"
@@ -165,7 +147,20 @@ def _get_hmac_prf(digest):
" generated by passlib.utils.pbkdf2.get_prf()" %
digest)
- if _EVP:
+ if _EVP and digest == "sha1":
+ # use m2crypto function directly for sha1, since that's it's default digest
+ try:
+ result = _EVP.hmac(b('x'),b('y'))
+ except ValueError: #pragma: no cover
+ pass
+ else:
+ if result == _XY_DIGEST:
+ return _EVP.hmac, 20
+ # pragma: no cover
+ # don't expect to ever get here, but will fall back to pure-python if we do.
+ warn("M2Crypto.EVP.HMAC() returned unexpected result "
+ "during Passlib self-test!", PasslibRuntimeWarning)
+ elif _EVP:
# use m2crypto if it's present and supports requested digest
try:
result = _EVP.hmac(b('x'), b('y'), digest)
@@ -194,8 +189,8 @@ def _get_hmac_prf(digest):
if len(key) > block_size:
key = digest_const(key).digest()
key += _BNULL * (block_size - len(key))
- tmp = digest_const(key.translate(trans_36) + msg).digest()
- return digest_const(key.translate(trans_5C) + tmp).digest()
+ tmp = digest_const(key.translate(_trans_36) + msg).digest()
+ return digest_const(key.translate(_trans_5C) + tmp).digest()
tag_wrapper(prf)
return prf, digest_size