summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2012-04-13 14:10:11 -0400
committerEli Collins <elic@assurancetechnologies.com>2012-04-13 14:10:11 -0400
commit5a3bd0d6ac8ad706c7d4a21aa49a51c9fcc54873 (patch)
tree185ad46852f88a753335d2c7bf662d0e0ef0c288
parentc0f420bf7d7659ee110432f7cbb0233554dfd32a (diff)
downloadpasslib-5a3bd0d6ac8ad706c7d4a21aa49a51c9fcc54873.tar.gz
work on des_crypt family
* cleaned up source of des_crypt variants and DES util functions * DES utils functions now have tighter input validation, full UT coverage
-rw-r--r--docs/lib/passlib.utils.des.rst2
-rw-r--r--docs/lib/passlib.utils.rst2
-rw-r--r--passlib/handlers/des_crypt.py213
-rw-r--r--passlib/tests/test_utils.py424
-rw-r--r--passlib/tests/test_utils_crypto.py550
-rw-r--r--passlib/utils/des.py353
6 files changed, 867 insertions, 677 deletions
diff --git a/docs/lib/passlib.utils.des.rst b/docs/lib/passlib.utils.des.rst
index 674f934..ea09506 100644
--- a/docs/lib/passlib.utils.des.rst
+++ b/docs/lib/passlib.utils.des.rst
@@ -18,4 +18,4 @@ since they are designed primarily for use in password hash algorithms
.. autofunction:: expand_des_key
.. autofunction:: des_encrypt_block
-.. autofunction:: mdes_encrypt_int_block
+.. autofunction:: des_encrypt_int_block
diff --git a/docs/lib/passlib.utils.rst b/docs/lib/passlib.utils.rst
index afd27de..060d420 100644
--- a/docs/lib/passlib.utils.rst
+++ b/docs/lib/passlib.utils.rst
@@ -3,7 +3,7 @@
=============================================
.. module:: passlib.utils
- :synopsis: helper functions for implementing password hashes
+ :synopsis: internal helpers for implementing password hashes
This module contains a number of utility functions used by passlib
to implement the builtin handlers, and other code within passlib.
diff --git a/passlib/handlers/des_crypt.py b/passlib/handlers/des_crypt.py
index 56102c0..d87c495 100644
--- a/passlib/handlers/des_crypt.py
+++ b/passlib/handlers/des_crypt.py
@@ -1,54 +1,4 @@
-"""passlib.handlers.des_crypt - traditional unix (DES) crypt and variants
-
-.. note::
-
- for des-crypt, passlib restricts salt characters to just the hash64 charset,
- and salt string size to >= 2 chars; since implementations of des-crypt
- vary in how they handle other characters / sizes...
-
- linux
-
- linux crypt() accepts salt characters outside the hash64 charset,
- and maps them using the following formula (determined by examining crypt's output):
- chr 0..64: v = (c-(1-19)) & 63 = (c+18) & 63
- chr 65..96: v = (c-(65-12)) & 63 = (c+11) & 63
- chr 97..127: v = (c-(97-38)) & 63 = (c+5) & 63
- chr 128..255: same as c-128
-
- invalid salt chars are mirrored back in the resulting hash.
-
- if the salt is too small, it uses a NUL char for the remaining
- character (which is treated the same as the char ``G``)
- when decoding the 12 bit salt. however, it outputs
- a hash string containing the single salt char twice,
- resulting in a corrupted hash.
-
- netbsd
-
- netbsd crypt() uses a 128-byte lookup table,
- which is only initialized for the hash64 values.
- the remaining values < 128 are implicitly zeroed,
- and values > 128 access past the array bounds
- (but seem to return 0).
-
- if the salt string is too small, it reads
- the NULL char (and continues past the end for bsdi crypt,
- though the buffer is usually large enough and NULLed).
- salt strings are output as provided,
- except for any NULs, which are converted to ``.``.
-
- openbsd, freebsd
-
- openbsd crypt() strictly defines the hash64 values as normal,
- and all other char values as 0. salt chars are reported as provided.
-
- if the salt or rounds string is too small,
- it'll read past the end, resulting in unpredictable
- values, though it'll terminate it's encoding
- of the output at the first null.
- this will generally result in a corrupted hash.
-"""
-
+"""passlib.handlers.des_crypt - traditional unix (DES) crypt and variants"""
#=========================================================
#imports
#=========================================================
@@ -60,7 +10,7 @@ from warnings import warn
#libs
from passlib.utils import classproperty, h64, h64big, safe_crypt, test_crypt, to_unicode
from passlib.utils.compat import b, bytes, byte_elem_value, u, uascii_to_str, unicode
-from passlib.utils.des import mdes_encrypt_int_block
+from passlib.utils.des import des_encrypt_int_block
import passlib.utils.handlers as uh
#pkg
#local
@@ -72,70 +22,86 @@ __all__ = [
]
#=========================================================
-#pure-python backend
+# pure-python backend for des_crypt family
#=========================================================
def _crypt_secret_to_key(secret):
- "crypt helper which converts lower 7 bits of first 8 chars of secret -> 56-bit des key, padded to 64 bits"
- return sum(
- (byte_elem_value(c) & 0x7f) << (57-8*i)
- for i, c in enumerate(secret[:8])
- )
-
-def raw_crypt(secret, salt):
- "pure-python fallback if stdlib support not present"
+ """convert secret to 64-bit DES key.
+
+ this only uses the first 8 bytes of the secret,
+ and discards the high 8th bit of each byte at that.
+ a null parity bit is inserted after every 7th bit of the output.
+ """
+ # NOTE: this would set the parity bits correctly,
+ # but des_encrypt_int_block() would just ignore them...
+ ##return sum(expand_7bit(byte_elem_value(c) & 0x7f) << (56-i*8)
+ ## for i, c in enumerate(secret[:8]))
+ return sum((byte_elem_value(c) & 0x7f) << (57-i*8)
+ for i, c in enumerate(secret[:8]))
+
+def _raw_des_crypt(secret, salt):
+ "pure-python backed for des_crypt"
assert len(salt) == 2
- #NOTE: technically could accept non-standard salts & single char salt,
- #but no official spec.
+ # NOTE: some OSes will accept non-HASH64 characters in the salt,
+ # but what value they assign these characters varies wildy,
+ # so just rejecting them outright.
+ # NOTE: the same goes for single-character salts...
+ # some OSes duplicate the char, some insert a '.' char,
+ # and openbsd does something which creates an invalid hash.
try:
salt_value = h64.decode_int12(salt)
except ValueError: #pragma: no cover - always caught by class
raise ValueError("invalid chars in salt")
- #FIXME: ^ this will throws error if bad salt chars are used
- # whereas linux crypt does something (inexplicable) with it
- #convert first 8 bytes of secret string into an integer
+ # forbidding NULL char because underlying crypt() rejects them too.
+ if b('\x00') in secret:
+ raise ValueError("null char in secret")
+
+ # convert first 8 bytes of secret string into an integer
key_value = _crypt_secret_to_key(secret)
- #run data through des using input of 0
- result = mdes_encrypt_int_block(key_value, 0, salt_value, 25)
+ # run data through des using input of 0
+ result = des_encrypt_int_block(key_value, 0, salt_value, 25)
- #run h64 encode on result
+ # run h64 encode on result
return h64big.encode_int64(result)
-def raw_ext_crypt(secret, rounds, salt):
- "ext_crypt() helper which returns checksum only"
+def _bsdi_secret_to_key(secret):
+ "covert secret to DES key used by bsdi_crypt"
+ key_value = _crypt_secret_to_key(secret)
+ idx = 8
+ end = len(secret)
+ while idx < end:
+ next = idx+8
+ tmp_value = _crypt_secret_to_key(secret[idx:next])
+ key_value = des_encrypt_int_block(key_value, key_value) ^ tmp_value
+ idx = next
+ return key_value
- #decode salt
+def _raw_bsdi_crypt(secret, rounds, salt):
+ "pure-python backend for bsdi_crypt"
+
+ # decode salt
try:
salt_value = h64.decode_int24(salt)
except ValueError: #pragma: no cover - always caught by class
raise ValueError("invalid salt")
- #validate secret
- if b('\x00') in secret: #pragma: no cover - always caught by class
- #builtin linux crypt doesn't like this, so we don't either
- #XXX: would make more sense to raise ValueError, but want to be compatible w/ stdlib crypt
+ # forbidding NULL char because underlying crypt() rejects them too.
+ if b('\x00') in secret:
raise ValueError("secret must be string without null bytes")
- #convert secret string into an integer
- key_value = _crypt_secret_to_key(secret)
- idx = 8
- end = len(secret)
- while idx < end:
- next = idx+8
- key_value = mdes_encrypt_int_block(key_value, key_value) ^ \
- _crypt_secret_to_key(secret[idx:next])
- idx = next
+ # convert secret string into an integer
+ key_value = _bsdi_secret_to_key(secret)
- #run data through des using input of 0
- result = mdes_encrypt_int_block(key_value, 0, salt_value, rounds)
+ # run data through des using input of 0
+ result = des_encrypt_int_block(key_value, 0, salt_value, rounds)
- #run h64 encode on result
+ # run h64 encode on result
return h64big.encode_int64(result)
#=========================================================
-#handler
+# handlers
#=========================================================
class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
"""This class implements the des-crypt password hash, and follows the :ref:`password-hash-api`.
@@ -156,9 +122,8 @@ class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
You can see which backend is in use by calling the :meth:`get_backend()` method.
"""
-
#=========================================================
- #class attrs
+ # class attrs
#=========================================================
#--GenericHandler--
name = "des_crypt"
@@ -170,7 +135,7 @@ class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
salt_chars = uh.HASH64_CHARS
#=========================================================
- #formatting
+ # formatting
#=========================================================
#FORMAT: 2 chars of H64-encoded salt + 11 chars of H64-encoded checksum
@@ -191,7 +156,7 @@ class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
return uascii_to_str(hash)
#=========================================================
- #backend
+ # backend
#=========================================================
backends = ("os_crypt", "builtin")
@@ -205,11 +170,7 @@ class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
# gotta do something - no official policy since des-crypt predates unicode
if isinstance(secret, unicode):
secret = secret.encode("utf-8")
- # forbidding nul chars because linux crypt (and most C implementations)
- # won't accept it either.
- if b('\x00') in secret:
- raise ValueError("null char in secret")
- return raw_crypt(secret, self.salt.encode("ascii")).decode("ascii")
+ return _raw_des_crypt(secret, self.salt.encode("ascii")).decode("ascii")
def _calc_checksum_os_crypt(self, secret):
# NOTE: safe_crypt encodes unicode secret -> utf8
@@ -222,19 +183,9 @@ class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
return self._calc_checksum_builtin(secret)
#=========================================================
- #eoc
+ # eoc
#=========================================================
-#=========================================================
-#handler
-#=========================================================
-
-#FIXME: phpass code notes that even rounds values should be avoided for BSDI-Crypt,
-# so as not to reveal weak des keys. given the random salt, this shouldn't be
-# a very likely issue anyways, but should do something about default rounds generation anyways.
-# http://wiki.call-cc.org/eggref/4/crypt sez even rounds of DES may reveal weak keys.
-# list of semi-weak keys - http://dolphinburger.com/cgi-bin/bsdi-man?proto=1.1&query=bdes&msection=1&apropos=0
-
class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler):
"""This class implements the BSDi-Crypt password hash, and follows the :ref:`password-hash-api`.
@@ -259,7 +210,7 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
You can see which backend is in use by calling the :meth:`get_backend()` method.
"""
#=========================================================
- #class attrs
+ # class attrs
#=========================================================
#--GenericHandler--
name = "bsdi_crypt"
@@ -281,7 +232,7 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
# but that seems to be an OS policy, not a algorithm limitation.
#=========================================================
- #internal helpers
+ # parsing
#=========================================================
_hash_regex = re.compile(u(r"""
^
@@ -323,7 +274,7 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
def _calc_checksum_builtin(self, secret):
if isinstance(secret, unicode):
secret = secret.encode("utf-8")
- return raw_ext_crypt(secret, self.rounds, self.salt.encode("ascii")).decode("ascii")
+ return _raw_bsdi_crypt(secret, self.rounds, self.salt.encode("ascii")).decode("ascii")
def _calc_checksum_os_crypt(self, secret):
config = self.to_string()
@@ -335,12 +286,9 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
return self._calc_checksum_builtin(secret)
#=========================================================
- #eoc
+ # eoc
#=========================================================
-#=========================================================
-#
-#=========================================================
class bigcrypt(uh.HasSalt, uh.GenericHandler):
"""This class implements the BigCrypt password hash, and follows the :ref:`password-hash-api`.
@@ -354,7 +302,7 @@ class bigcrypt(uh.HasSalt, uh.GenericHandler):
If specified, it must be 22 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
"""
#=========================================================
- #class attrs
+ # class attrs
#=========================================================
#--GenericHandler--
name = "bigcrypt"
@@ -367,7 +315,7 @@ class bigcrypt(uh.HasSalt, uh.GenericHandler):
salt_chars = uh.HASH64_CHARS
#=========================================================
- #internal helpers
+ # internal helpers
#=========================================================
_hash_regex = re.compile(u(r"""
^
@@ -395,29 +343,24 @@ class bigcrypt(uh.HasSalt, uh.GenericHandler):
return value
#=========================================================
- #backend
+ # backend
#=========================================================
- #TODO: check if os_crypt supports ext-des-crypt.
-
def _calc_checksum(self, secret):
if isinstance(secret, unicode):
secret = secret.encode("utf-8")
- chk = raw_crypt(secret, self.salt.encode("ascii"))
+ chk = _raw_des_crypt(secret, self.salt.encode("ascii"))
idx = 8
end = len(secret)
while idx < end:
next = idx + 8
- chk += raw_crypt(secret[idx:next], chk[-11:-9])
+ chk += _raw_des_crypt(secret[idx:next], chk[-11:-9])
idx = next
return chk.decode("ascii")
#=========================================================
- #eoc
+ # eoc
#=========================================================
-#=========================================================
-#
-#=========================================================
class crypt16(uh.HasSalt, uh.GenericHandler):
"""This class implements the crypt16 password hash, and follows the :ref:`password-hash-api`.
@@ -431,7 +374,7 @@ class crypt16(uh.HasSalt, uh.GenericHandler):
If specified, it must be 2 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
"""
#=========================================================
- #class attrs
+ # class attrs
#=========================================================
#--GenericHandler--
name = "crypt16"
@@ -444,7 +387,7 @@ class crypt16(uh.HasSalt, uh.GenericHandler):
salt_chars = uh.HASH64_CHARS
#=========================================================
- #internal helpers
+ # internal helpers
#=========================================================
_hash_regex = re.compile(u(r"""
^
@@ -466,10 +409,8 @@ class crypt16(uh.HasSalt, uh.GenericHandler):
return uascii_to_str(hash)
#=========================================================
- #backend
+ # backend
#=========================================================
- #TODO: check if os_crypt supports ext-des-crypt.
-
def _calc_checksum(self, secret):
if isinstance(secret, unicode):
secret = secret.encode("utf-8")
@@ -484,22 +425,22 @@ class crypt16(uh.HasSalt, uh.GenericHandler):
key1 = _crypt_secret_to_key(secret)
#run data through des using input of 0
- result1 = mdes_encrypt_int_block(key1, 0, salt_value, 20)
+ result1 = des_encrypt_int_block(key1, 0, salt_value, 20)
#convert next 8 bytes of secret string into integer (key=0 if secret < 8 chars)
key2 = _crypt_secret_to_key(secret[8:16])
#run data through des using input of 0
- result2 = mdes_encrypt_int_block(key2, 0, salt_value, 5)
+ result2 = des_encrypt_int_block(key2, 0, salt_value, 5)
#done
chk = h64big.encode_int64(result1) + h64big.encode_int64(result2)
return chk.decode("ascii")
#=========================================================
- #eoc
+ # eoc
#=========================================================
#=========================================================
-#eof
+# eof
#=========================================================
diff --git a/passlib/tests/test_utils.py b/passlib/tests/test_utils.py
index 4f6b62d..001524d 100644
--- a/passlib/tests/test_utils.py
+++ b/passlib/tests/test_utils.py
@@ -500,84 +500,6 @@ class CodecTest(TestCase):
self.assertFalse(is_same_codec("ascii", "utf-8"))
#=========================================================
-#test des module
-#=========================================================
-import passlib.utils.des as des
-
-class DesTest(TestCase):
-
- #test vectors taken from http://www.skepticfiles.org/faq/testdes.htm
-
- #data is list of (key, plaintext, ciphertext), all as 64 bit hex string
- test_des_vectors = [
- (line[4:20], line[21:37], line[38:54])
- for line in
-b(""" 0000000000000000 0000000000000000 8CA64DE9C1B123A7
- FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 7359B2163E4EDC58
- 3000000000000000 1000000000000001 958E6E627A05557B
- 1111111111111111 1111111111111111 F40379AB9E0EC533
- 0123456789ABCDEF 1111111111111111 17668DFC7292532D
- 1111111111111111 0123456789ABCDEF 8A5AE1F81AB8F2DD
- 0000000000000000 0000000000000000 8CA64DE9C1B123A7
- FEDCBA9876543210 0123456789ABCDEF ED39D950FA74BCC4
- 7CA110454A1A6E57 01A1D6D039776742 690F5B0D9A26939B
- 0131D9619DC1376E 5CD54CA83DEF57DA 7A389D10354BD271
- 07A1133E4A0B2686 0248D43806F67172 868EBB51CAB4599A
- 3849674C2602319E 51454B582DDF440A 7178876E01F19B2A
- 04B915BA43FEB5B6 42FD443059577FA2 AF37FB421F8C4095
- 0113B970FD34F2CE 059B5E0851CF143A 86A560F10EC6D85B
- 0170F175468FB5E6 0756D8E0774761D2 0CD3DA020021DC09
- 43297FAD38E373FE 762514B829BF486A EA676B2CB7DB2B7A
- 07A7137045DA2A16 3BDD119049372802 DFD64A815CAF1A0F
- 04689104C2FD3B2F 26955F6835AF609A 5C513C9C4886C088
- 37D06BB516CB7546 164D5E404F275232 0A2AEEAE3FF4AB77
- 1F08260D1AC2465E 6B056E18759F5CCA EF1BF03E5DFA575A
- 584023641ABA6176 004BD6EF09176062 88BF0DB6D70DEE56
- 025816164629B007 480D39006EE762F2 A1F9915541020B56
- 49793EBC79B3258F 437540C8698F3CFA 6FBF1CAFCFFD0556
- 4FB05E1515AB73A7 072D43A077075292 2F22E49BAB7CA1AC
- 49E95D6D4CA229BF 02FE55778117F12A 5A6B612CC26CCE4A
- 018310DC409B26D6 1D9D5C5018F728C2 5F4C038ED12B2E41
- 1C587F1C13924FEF 305532286D6F295A 63FAC0D034D9F793
- 0101010101010101 0123456789ABCDEF 617B3A0CE8F07100
- 1F1F1F1F0E0E0E0E 0123456789ABCDEF DB958605F8C8C606
- E0FEE0FEF1FEF1FE 0123456789ABCDEF EDBFD1C66C29CCC7
- 0000000000000000 FFFFFFFFFFFFFFFF 355550B2150E2451
- FFFFFFFFFFFFFFFF 0000000000000000 CAAAAF4DEAF1DBAE
- 0123456789ABCDEF 0000000000000000 D5D44FF720683D0D
- FEDCBA9876543210 FFFFFFFFFFFFFFFF 2A2BB008DF97C2F2
- """).split(b("\n")) if line.strip()
- ]
-
- def test_des_encrypt_block(self):
- for k,p,c in self.test_des_vectors:
- k = unhexlify(k)
- p = unhexlify(p)
- c = unhexlify(c)
- result = des.des_encrypt_block(k,p)
- self.assertEqual(result, c, "key=%r p=%r:" % (k,p))
-
- #test 7 byte key
- #FIXME: use a better key
- k,p,c = b('00000000000000'), b('FFFFFFFFFFFFFFFF'), b('355550B2150E2451')
- k = unhexlify(k)
- p = unhexlify(p)
- c = unhexlify(c)
- result = des.des_encrypt_block(k,p)
- self.assertEqual(result, c, "key=%r p=%r:" % (k,p))
-
- def test_mdes_encrypt_int_block(self):
- for k,p,c in self.test_des_vectors:
- k = int(k,16)
- p = int(p,16)
- c = int(c,16)
- result = des.mdes_encrypt_int_block(k,p, salt=0, rounds=1)
- self.assertEqual(result, c, "key=%r p=%r:" % (k,p))
-
- #TODO: test other des methods (eg: mdes_encrypt_int_block w/ salt & rounds)
- # though des-crypt builtin backend test should thump it well enough
-
-#=========================================================
# base64engine
#=========================================================
class Base64EngineTest(TestCase):
@@ -971,351 +893,5 @@ class H64Big_Test(_Base64Test):
]
#=========================================================
-#test md4
-#=========================================================
-class _MD4_Test(TestCase):
- #test vectors from http://www.faqs.org/rfcs/rfc1320.html - A.5
-
- hash = None
-
- vectors = [
- # input -> hex digest
- (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"
- md4 = self.hash
- h = md4(b(''))
- self.assertEqual(h.hexdigest(), "31d6cfe0d16ae931b73c59d7e0c089c0")
-
- #NOTE: under py2, hashlib methods try to encode to ascii,
- # though shouldn't rely on that.
- if PY3:
- self.assertRaises(TypeError, h.update, u('x'))
-
- h.update(b('a'))
- self.assertEqual(h.hexdigest(), "bde52cb31de33e46245e05fbdbd6fb24")
-
- h.update(b('bcdefghijklmnopqrstuvwxyz'))
- self.assertEqual(h.hexdigest(), "d79e1c308aa5bbcdeea8ed63df412da9")
-
- def test_md4_hexdigest(self):
- "test md4 hexdigest()"
- md4 = self.hash
- for input, hex in self.vectors:
- out = md4(input).hexdigest()
- self.assertEqual(out, hex)
-
- def test_md4_digest(self):
- "test md4 digest()"
- md4 = self.hash
- for input, hex in self.vectors:
- out = bascii_to_str(hexlify(md4(input).digest()))
- self.assertEqual(out, hex)
-
- def test_md4_copy(self):
- "test md4 copy()"
- md4 = self.hash
- h = md4(b('abc'))
-
- h2 = h.copy()
- h2.update(b('def'))
- self.assertEqual(h2.hexdigest(), '804e7f1c2586e50b49ac65db5b645131')
-
- h.update(b('ghi'))
- self.assertEqual(h.hexdigest(), 'c5225580bfe176f6deeee33dee98732c')
-
-#
-#now do a bunch of things to test multiple possible backends.
-#
-import passlib.utils.md4 as md4_mod
-
-has_ssl_md4 = (md4_mod.md4 is not md4_mod._builtin_md4)
-
-if has_ssl_md4:
- class MD4_SSL_Test(_MD4_Test):
- descriptionPrefix = "MD4 (SSL version)"
- hash = staticmethod(md4_mod.md4)
-
-if not has_ssl_md4 or enable_option("cover"):
- class MD4_Builtin_Test(_MD4_Test):
- descriptionPrefix = "MD4 (builtin version)"
- hash = md4_mod._builtin_md4
-
-#=========================================================
-#test passlib.utils.pbkdf2
-#=========================================================
-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"
-
- ndn_formats = ["hashlib", "iana"]
- ndn_values = [
- # (iana name, hashlib name, ... other unnormalized names)
- ("md5", "md5", "SCRAM-MD5-PLUS", "MD-5"),
- ("sha1", "sha-1", "SCRAM-SHA-1", "SHA1"),
- ("sha256", "sha-256", "SHA_256", "sha2-256"),
- ("ripemd", "ripemd", "SCRAM-RIPEMD", "RIPEMD"),
- ("ripemd160", "ripemd-160",
- "SCRAM-RIPEMD-160", "RIPEmd160"),
- ("test128", "test-128", "TEST128"),
- ("test2", "test2", "TEST-2"),
- ("test3128", "test3-128", "TEST-3-128"),
- ]
-
- def test_norm_hash_name(self):
- "test norm_hash_name()"
- from itertools import chain
- from passlib.utils.pbkdf2 import norm_hash_name, _nhn_hash_names
-
- # test formats
- for format in self.ndn_formats:
- norm_hash_name("md4", format)
- self.assertRaises(ValueError, norm_hash_name, "md4", None)
- self.assertRaises(ValueError, norm_hash_name, "md4", "fake")
-
- # test types
- self.assertEqual(norm_hash_name(u("MD4")), "md4")
- self.assertEqual(norm_hash_name(b("MD4")), "md4")
- self.assertRaises(TypeError, norm_hash_name, None)
-
- # test selected results
- with catch_warnings():
- warnings.filterwarnings("ignore", '.*unknown hash')
- for row in chain(_nhn_hash_names, self.ndn_values):
- for idx, format in enumerate(self.ndn_formats):
- correct = row[idx]
- for value in row:
- result = norm_hash_name(value, format)
- self.assertEqual(result, correct,
- "name=%r, format=%r:" % (value,
- format))
-
-class KdfTest(TestCase):
- "test kdf helpers"
-
- def test_pbkdf1(self):
- "test pbkdf1"
- for secret, salt, rounds, klen, hash, correct in [
- #http://www.di-mgt.com.au/cryptoKDFs.html
- (b('password'), hb('78578E5A5D63CB06'), 1000, 16, 'sha1',
- hb('dc19847e05c64d2faf10ebfb4a3d2a20')),
- ]:
- result = pbkdf2.pbkdf1(secret, salt, rounds, klen, hash)
- self.assertEqual(result, correct)
-
- #test rounds < 1
- #test klen < 0
- #test klen > block size
- #test invalid hash
-
-#NOTE: this is not run directly, but via two subclasses (below)
-class _Pbkdf2BackendTest(TestCase):
- "test builtin unix crypt backend"
- enable_m2crypto = False
-
- def setUp(self):
- #disable m2crypto support so we'll always use software backend
- if not self.enable_m2crypto:
- self._orig_EVP = pbkdf2._EVP
- pbkdf2._EVP = None
- else:
- #set flag so tests can check for m2crypto presence quickly
- self.enable_m2crypto = bool(pbkdf2._EVP)
- pbkdf2._clear_prf_cache()
-
- def tearDown(self):
- if not self.enable_m2crypto:
- pbkdf2._EVP = self._orig_EVP
- pbkdf2._clear_prf_cache()
-
- #TODO: test get_prf() behavior in various situations - though overall behavior tested via pbkdf2
-
- def test_rfc3962(self):
- "rfc3962 test vectors"
- self.assertFunctionResults(pbkdf2.pbkdf2, [
- # result, secret, salt, rounds, keylen, digest="sha1"
-
- #test case 1 / 128 bit
- (
- hb("cdedb5281bb2f801565a1122b2563515"),
- b("password"), b("ATHENA.MIT.EDUraeburn"), 1, 16
- ),
-
- #test case 2 / 128 bit
- (
- hb("01dbee7f4a9e243e988b62c73cda935d"),
- b("password"), b("ATHENA.MIT.EDUraeburn"), 2, 16
- ),
-
- #test case 2 / 256 bit
- (
- hb("01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86"),
- b("password"), b("ATHENA.MIT.EDUraeburn"), 2, 32
- ),
-
- #test case 3 / 256 bit
- (
- hb("5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13"),
- b("password"), b("ATHENA.MIT.EDUraeburn"), 1200, 32
- ),
-
- #test case 4 / 256 bit
- (
- hb("d1daa78615f287e6a1c8b120d7062a493f98d203e6be49a6adf4fa574b6e64ee"),
- b("password"), b('\x12\x34\x56\x78\x78\x56\x34\x12'), 5, 32
- ),
-
- #test case 5 / 256 bit
- (
- hb("139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1"),
- b("X"*64), b("pass phrase equals block size"), 1200, 32
- ),
-
- #test case 6 / 256 bit
- (
- hb("9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a"),
- b("X"*65), b("pass phrase exceeds block size"), 1200, 32
- ),
- ])
-
- def test_rfc6070(self):
- "rfc6070 test vectors"
- self.assertFunctionResults(pbkdf2.pbkdf2, [
-
- (
- hb("0c60c80f961f0e71f3a9b524af6012062fe037a6"),
- b("password"), b("salt"), 1, 20,
- ),
-
- (
- hb("ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"),
- b("password"), b("salt"), 2, 20,
- ),
-
- (
- hb("4b007901b765489abead49d926f721d065a429c1"),
- b("password"), b("salt"), 4096, 20,
- ),
-
- #just runs too long - could enable if ALL option is set
- ##(
- ##
- ## unhexlify("eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"),
- ## "password", "salt", 16777216, 20,
- ##),
-
- (
- hb("3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"),
- b("passwordPASSWORDpassword"),
- b("saltSALTsaltSALTsaltSALTsaltSALTsalt"),
- 4096, 25,
- ),
-
- (
- hb("56fa6aa75548099dcc37d7f03425e0c3"),
- b("pass\00word"), b("sa\00lt"), 4096, 16,
- ),
- ])
-
- def test_invalid_values(self):
-
- #invalid rounds
- 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, b('password'), b('salt'),
- 1, 20*(2**32-1)+1)
-
- #invalid salt type
- self.assertRaises(TypeError, pbkdf2.pbkdf2, b('password'), 5, 1, 10)
-
- #invalid secret type
- self.assertRaises(TypeError, pbkdf2.pbkdf2, 5, b('salt'), 1, 10)
-
- #invalid hash
- 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_default_keylen(self):
- "test keylen==-1"
- self.assertEqual(len(pbkdf2.pbkdf2(b('password'), b('salt'), 1, -1,
- prf='hmac-sha1')), 20)
-
- self.assertEqual(len(pbkdf2.pbkdf2(b('password'), b('salt'), 1, -1,
- prf='hmac-sha256')), 32)
-
- def test_hmac_sha1(self):
- "test independant hmac_sha1() method"
- self.assertEqual(
- 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_sha1_string(self):
- "test various prf values"
- self.assertEqual(
- pbkdf2.pbkdf2(b("secret"), b("salt"), 10, 16, "hmac-sha1"),
- b('\xe2H\xfbk\x136QF\xf8\xacc\x07\xcc"(\x12')
- )
-
- def test_sha512_string(self):
- "test alternate digest string (sha512)"
- self.assertFunctionResults(pbkdf2.pbkdf2, [
- # result, secret, salt, rounds, keylen, digest="sha1"
-
- #case taken from example in http://grub.enbug.org/Authentication
- (
- hb("887CFF169EA8335235D8004242AA7D6187A41E3187DF0CE14E256D85ED97A97357AAA8FF0A3871AB9EEFF458392F462F495487387F685B7472FC6C29E293F0A0"),
- b("hello"),
- hb("9290F727ED06C38BA4549EF7DE25CF5642659211B7FC076F2D28FEFD71784BB8D8F6FB244A8CC5C06240631B97008565A120764C0EE9C2CB0073994D79080136"),
- 10000, 64, "hmac-sha512"
- ),
- ])
-
- def test_sha512_function(self):
- "test custom digest function"
- def prf(key, msg):
- return hmac.new(key, msg, hashlib.sha512).digest()
-
- self.assertFunctionResults(pbkdf2.pbkdf2, [
- # result, secret, salt, rounds, keylen, digest="sha1"
-
- #case taken from example in http://grub.enbug.org/Authentication
- (
- hb("887CFF169EA8335235D8004242AA7D6187A41E3187DF0CE14E256D85ED97A97357AAA8FF0A3871AB9EEFF458392F462F495487387F685B7472FC6C29E293F0A0"),
- b("hello"),
- hb("9290F727ED06C38BA4549EF7DE25CF5642659211B7FC076F2D28FEFD71784BB8D8F6FB244A8CC5C06240631B97008565A120764C0EE9C2CB0073994D79080136"),
- 10000, 64, prf,
- ),
- ])
-
-has_m2crypto = (pbkdf2._EVP is not None)
-
-if has_m2crypto:
- class Pbkdf2_M2Crypto_Test(_Pbkdf2BackendTest):
- descriptionPrefix = "pbkdf2 (m2crypto backend)"
- enable_m2crypto = True
-
-if not has_m2crypto or enable_option("cover"):
- class Pbkdf2_Builtin_Test(_Pbkdf2BackendTest):
- descriptionPrefix = "pbkdf2 (builtin backend)"
- enable_m2crypto = False
-
-#=========================================================
#EOF
#=========================================================
diff --git a/passlib/tests/test_utils_crypto.py b/passlib/tests/test_utils_crypto.py
new file mode 100644
index 0000000..94c20e8
--- /dev/null
+++ b/passlib/tests/test_utils_crypto.py
@@ -0,0 +1,550 @@
+"""tests for passlib.utils.(des|pbkdf2|md4)"""
+#=========================================================
+#imports
+#=========================================================
+from __future__ import with_statement
+#core
+from binascii import hexlify, unhexlify
+import sys
+import random
+import warnings
+#site
+#pkg
+#module
+from passlib.utils.compat import b, bytes, bascii_to_str, irange, PY2, PY3, u, \
+ unicode, join_bytes
+from passlib.tests.utils import TestCase, Params as ak, enable_option, catch_warnings
+
+#=========================================================
+# support
+#=========================================================
+def hb(source):
+ return unhexlify(b(source))
+
+#=========================================================
+#test des module
+#=========================================================
+class DesTest(TestCase):
+
+ # test vectors taken from http://www.skepticfiles.org/faq/testdes.htm
+ des_test_vectors = [
+ # key, plaintext, ciphertext
+ (0x0000000000000000, 0x0000000000000000, 0x8CA64DE9C1B123A7),
+ (0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0x7359B2163E4EDC58),
+ (0x3000000000000000, 0x1000000000000001, 0x958E6E627A05557B),
+ (0x1111111111111111, 0x1111111111111111, 0xF40379AB9E0EC533),
+ (0x0123456789ABCDEF, 0x1111111111111111, 0x17668DFC7292532D),
+ (0x1111111111111111, 0x0123456789ABCDEF, 0x8A5AE1F81AB8F2DD),
+ (0x0000000000000000, 0x0000000000000000, 0x8CA64DE9C1B123A7),
+ (0xFEDCBA9876543210, 0x0123456789ABCDEF, 0xED39D950FA74BCC4),
+ (0x7CA110454A1A6E57, 0x01A1D6D039776742, 0x690F5B0D9A26939B),
+ (0x0131D9619DC1376E, 0x5CD54CA83DEF57DA, 0x7A389D10354BD271),
+ (0x07A1133E4A0B2686, 0x0248D43806F67172, 0x868EBB51CAB4599A),
+ (0x3849674C2602319E, 0x51454B582DDF440A, 0x7178876E01F19B2A),
+ (0x04B915BA43FEB5B6, 0x42FD443059577FA2, 0xAF37FB421F8C4095),
+ (0x0113B970FD34F2CE, 0x059B5E0851CF143A, 0x86A560F10EC6D85B),
+ (0x0170F175468FB5E6, 0x0756D8E0774761D2, 0x0CD3DA020021DC09),
+ (0x43297FAD38E373FE, 0x762514B829BF486A, 0xEA676B2CB7DB2B7A),
+ (0x07A7137045DA2A16, 0x3BDD119049372802, 0xDFD64A815CAF1A0F),
+ (0x04689104C2FD3B2F, 0x26955F6835AF609A, 0x5C513C9C4886C088),
+ (0x37D06BB516CB7546, 0x164D5E404F275232, 0x0A2AEEAE3FF4AB77),
+ (0x1F08260D1AC2465E, 0x6B056E18759F5CCA, 0xEF1BF03E5DFA575A),
+ (0x584023641ABA6176, 0x004BD6EF09176062, 0x88BF0DB6D70DEE56),
+ (0x025816164629B007, 0x480D39006EE762F2, 0xA1F9915541020B56),
+ (0x49793EBC79B3258F, 0x437540C8698F3CFA, 0x6FBF1CAFCFFD0556),
+ (0x4FB05E1515AB73A7, 0x072D43A077075292, 0x2F22E49BAB7CA1AC),
+ (0x49E95D6D4CA229BF, 0x02FE55778117F12A, 0x5A6B612CC26CCE4A),
+ (0x018310DC409B26D6, 0x1D9D5C5018F728C2, 0x5F4C038ED12B2E41),
+ (0x1C587F1C13924FEF, 0x305532286D6F295A, 0x63FAC0D034D9F793),
+ (0x0101010101010101, 0x0123456789ABCDEF, 0x617B3A0CE8F07100),
+ (0x1F1F1F1F0E0E0E0E, 0x0123456789ABCDEF, 0xDB958605F8C8C606),
+ (0xE0FEE0FEF1FEF1FE, 0x0123456789ABCDEF, 0xEDBFD1C66C29CCC7),
+ (0x0000000000000000, 0xFFFFFFFFFFFFFFFF, 0x355550B2150E2451),
+ (0xFFFFFFFFFFFFFFFF, 0x0000000000000000, 0xCAAAAF4DEAF1DBAE),
+ (0x0123456789ABCDEF, 0x0000000000000000, 0xD5D44FF720683D0D),
+ (0xFEDCBA9876543210, 0xFFFFFFFFFFFFFFFF, 0x2A2BB008DF97C2F2),
+ ]
+
+ def test_01_expand(self):
+ "test expand_des_key()"
+ from passlib.utils.des import expand_des_key, shrink_des_key, \
+ _KDATA_MASK, INT_56_MASK
+
+ # make sure test vectors are preserved (sans parity bits)
+ # uses ints, bytes are tested under #02
+ for key1, _, _ in self.des_test_vectors:
+ key2 = shrink_des_key(key1)
+ key3 = expand_des_key(key2)
+ # NOTE: this assumes expand_des_key() sets parity bits to 0
+ self.assertEqual(key3, key1 & _KDATA_MASK)
+
+ # type checks
+ self.assertRaises(TypeError, expand_des_key, 1.0)
+
+ # too large
+ self.assertRaises(ValueError, expand_des_key, INT_56_MASK+1)
+ self.assertRaises(ValueError, expand_des_key, b("\x00")*8)
+
+ # too small
+ self.assertRaises(ValueError, expand_des_key, -1)
+ self.assertRaises(ValueError, expand_des_key, b("\x00")*6)
+
+ def test_02_shrink(self):
+ "test shrink_des_key()"
+ from passlib.utils.des import expand_des_key, shrink_des_key, \
+ INT_64_MASK
+ from passlib.utils import random, getrandbytes
+
+ # make sure reverse works for some random keys
+ # uses bytes, ints are tested under #01
+ for i in range(20):
+ key1 = getrandbytes(random, 7)
+ key2 = expand_des_key(key1)
+ key3 = shrink_des_key(key2)
+ self.assertEqual(key3, key1)
+
+ # type checks
+ self.assertRaises(TypeError, shrink_des_key, 1.0)
+
+ # too large
+ self.assertRaises(ValueError, shrink_des_key, INT_64_MASK+1)
+ self.assertRaises(ValueError, shrink_des_key, b("\x00")*9)
+
+ # too small
+ self.assertRaises(ValueError, shrink_des_key, -1)
+ self.assertRaises(ValueError, shrink_des_key, b("\x00")*7)
+
+ def _random_parity(self, key):
+ "randomize parity bits"
+ from passlib.utils.des import _KDATA_MASK, _KPARITY_MASK, INT_64_MASK
+ from passlib.utils import rng
+ return (key & _KDATA_MASK) | (rng.randint(0,INT_64_MASK) & _KPARITY_MASK)
+
+ def test_03_encrypt_bytes(self):
+ "test des_encrypt_block()"
+ from passlib.utils.des import (des_encrypt_block, shrink_des_key,
+ _pack64, _unpack64)
+
+ # run through test vectors
+ for key, plaintext, correct in self.des_test_vectors:
+ # convert to bytes
+ key = _pack64(key)
+ plaintext = _pack64(plaintext)
+ correct = _pack64(correct)
+
+ # test 64-bit key
+ result = des_encrypt_block(key, plaintext)
+ self.assertEqual(result, correct, "key=%r plaintext=%r:" %
+ (key, plaintext))
+
+ # test 56-bit version
+ key2 = shrink_des_key(key)
+ result = des_encrypt_block(key2, plaintext)
+ self.assertEqual(result, correct, "key=%r shrink(key)=%r plaintext=%r:" %
+ (key, key2, plaintext))
+
+ # test with random parity bits
+ for _ in range(20):
+ key3 = _pack64(self._random_parity(_unpack64(key)))
+ result = des_encrypt_block(key3, plaintext)
+ self.assertEqual(result, correct, "key=%r rndparity(key)=%r plaintext=%r:" %
+ (key, key3, plaintext))
+
+ # check invalid keys
+ stub = b('\x00') * 8
+ self.assertRaises(TypeError, des_encrypt_block, 0, stub)
+ self.assertRaises(ValueError, des_encrypt_block, b('\x00')*6, stub)
+
+ # check invalid input
+ self.assertRaises(TypeError, des_encrypt_block, stub, 0)
+ self.assertRaises(ValueError, des_encrypt_block, stub, b('\x00')*7)
+
+ # check invalid salts
+ self.assertRaises(ValueError, des_encrypt_block, stub, stub, salt=-1)
+ self.assertRaises(ValueError, des_encrypt_block, stub, stub, salt=1<<24)
+
+ # check invalid rounds
+ self.assertRaises(ValueError, des_encrypt_block, stub, stub, 0, rounds=0)
+
+ def test_04_encrypt_ints(self):
+ "test des_encrypt_int_block()"
+ from passlib.utils.des import (des_encrypt_int_block, shrink_des_key)
+
+ # run through test vectors
+ for key, plaintext, correct in self.des_test_vectors:
+ # test 64-bit key
+ result = des_encrypt_int_block(key, plaintext)
+ self.assertEqual(result, correct, "key=%r plaintext=%r:" %
+ (key, plaintext))
+
+ # test with random parity bits
+ for _ in range(20):
+ key3 = self._random_parity(key)
+ result = des_encrypt_int_block(key3, plaintext)
+ self.assertEqual(result, correct, "key=%r rndparity(key)=%r plaintext=%r:" %
+ (key, key3, plaintext))
+
+ # check invalid keys
+ self.assertRaises(TypeError, des_encrypt_int_block, b('\x00'), 0)
+ self.assertRaises(ValueError, des_encrypt_int_block, -1, 0)
+
+ # check invalid input
+ self.assertRaises(TypeError, des_encrypt_int_block, 0, b('\x00'))
+ self.assertRaises(ValueError, des_encrypt_int_block, 0, -1)
+
+ # check invalid salts
+ self.assertRaises(ValueError, des_encrypt_int_block, 0, 0, salt=-1)
+ self.assertRaises(ValueError, des_encrypt_int_block, 0, 0, salt=1<<24)
+
+ # check invalid rounds
+ self.assertRaises(ValueError, des_encrypt_int_block, 0, 0, 0, rounds=0)
+
+#=========================================================
+#test md4
+#=========================================================
+class _MD4_Test(TestCase):
+ #test vectors from http://www.faqs.org/rfcs/rfc1320.html - A.5
+
+ hash = None
+
+ vectors = [
+ # input -> hex digest
+ (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"
+ md4 = self.hash
+ h = md4(b(''))
+ self.assertEqual(h.hexdigest(), "31d6cfe0d16ae931b73c59d7e0c089c0")
+
+ #NOTE: under py2, hashlib methods try to encode to ascii,
+ # though shouldn't rely on that.
+ if PY3:
+ self.assertRaises(TypeError, h.update, u('x'))
+
+ h.update(b('a'))
+ self.assertEqual(h.hexdigest(), "bde52cb31de33e46245e05fbdbd6fb24")
+
+ h.update(b('bcdefghijklmnopqrstuvwxyz'))
+ self.assertEqual(h.hexdigest(), "d79e1c308aa5bbcdeea8ed63df412da9")
+
+ def test_md4_hexdigest(self):
+ "test md4 hexdigest()"
+ md4 = self.hash
+ for input, hex in self.vectors:
+ out = md4(input).hexdigest()
+ self.assertEqual(out, hex)
+
+ def test_md4_digest(self):
+ "test md4 digest()"
+ md4 = self.hash
+ for input, hex in self.vectors:
+ out = bascii_to_str(hexlify(md4(input).digest()))
+ self.assertEqual(out, hex)
+
+ def test_md4_copy(self):
+ "test md4 copy()"
+ md4 = self.hash
+ h = md4(b('abc'))
+
+ h2 = h.copy()
+ h2.update(b('def'))
+ self.assertEqual(h2.hexdigest(), '804e7f1c2586e50b49ac65db5b645131')
+
+ h.update(b('ghi'))
+ self.assertEqual(h.hexdigest(), 'c5225580bfe176f6deeee33dee98732c')
+
+#
+#now do a bunch of things to test multiple possible backends.
+#
+import passlib.utils.md4 as md4_mod
+
+has_ssl_md4 = (md4_mod.md4 is not md4_mod._builtin_md4)
+
+if has_ssl_md4:
+ class MD4_SSL_Test(_MD4_Test):
+ descriptionPrefix = "MD4 (SSL version)"
+ hash = staticmethod(md4_mod.md4)
+
+if not has_ssl_md4 or enable_option("cover"):
+ class MD4_Builtin_Test(_MD4_Test):
+ descriptionPrefix = "MD4 (builtin version)"
+ hash = md4_mod._builtin_md4
+
+#=========================================================
+#test passlib.utils.pbkdf2
+#=========================================================
+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"
+
+ ndn_formats = ["hashlib", "iana"]
+ ndn_values = [
+ # (iana name, hashlib name, ... other unnormalized names)
+ ("md5", "md5", "SCRAM-MD5-PLUS", "MD-5"),
+ ("sha1", "sha-1", "SCRAM-SHA-1", "SHA1"),
+ ("sha256", "sha-256", "SHA_256", "sha2-256"),
+ ("ripemd", "ripemd", "SCRAM-RIPEMD", "RIPEMD"),
+ ("ripemd160", "ripemd-160",
+ "SCRAM-RIPEMD-160", "RIPEmd160"),
+ ("test128", "test-128", "TEST128"),
+ ("test2", "test2", "TEST-2"),
+ ("test3128", "test3-128", "TEST-3-128"),
+ ]
+
+ def test_norm_hash_name(self):
+ "test norm_hash_name()"
+ from itertools import chain
+ from passlib.utils.pbkdf2 import norm_hash_name, _nhn_hash_names
+
+ # test formats
+ for format in self.ndn_formats:
+ norm_hash_name("md4", format)
+ self.assertRaises(ValueError, norm_hash_name, "md4", None)
+ self.assertRaises(ValueError, norm_hash_name, "md4", "fake")
+
+ # test types
+ self.assertEqual(norm_hash_name(u("MD4")), "md4")
+ self.assertEqual(norm_hash_name(b("MD4")), "md4")
+ self.assertRaises(TypeError, norm_hash_name, None)
+
+ # test selected results
+ with catch_warnings():
+ warnings.filterwarnings("ignore", '.*unknown hash')
+ for row in chain(_nhn_hash_names, self.ndn_values):
+ for idx, format in enumerate(self.ndn_formats):
+ correct = row[idx]
+ for value in row:
+ result = norm_hash_name(value, format)
+ self.assertEqual(result, correct,
+ "name=%r, format=%r:" % (value,
+ format))
+
+class KdfTest(TestCase):
+ "test kdf helpers"
+
+ def test_pbkdf1(self):
+ "test pbkdf1"
+ for secret, salt, rounds, klen, hash, correct in [
+ #http://www.di-mgt.com.au/cryptoKDFs.html
+ (b('password'), hb('78578E5A5D63CB06'), 1000, 16, 'sha1',
+ hb('dc19847e05c64d2faf10ebfb4a3d2a20')),
+ ]:
+ result = pbkdf2.pbkdf1(secret, salt, rounds, klen, hash)
+ self.assertEqual(result, correct)
+
+ #test rounds < 1
+ #test klen < 0
+ #test klen > block size
+ #test invalid hash
+
+#NOTE: this is not run directly, but via two subclasses (below)
+class _Pbkdf2BackendTest(TestCase):
+ "test builtin unix crypt backend"
+ enable_m2crypto = False
+
+ def setUp(self):
+ #disable m2crypto support so we'll always use software backend
+ if not self.enable_m2crypto:
+ self._orig_EVP = pbkdf2._EVP
+ pbkdf2._EVP = None
+ else:
+ #set flag so tests can check for m2crypto presence quickly
+ self.enable_m2crypto = bool(pbkdf2._EVP)
+ pbkdf2._clear_prf_cache()
+
+ def tearDown(self):
+ if not self.enable_m2crypto:
+ pbkdf2._EVP = self._orig_EVP
+ pbkdf2._clear_prf_cache()
+
+ #TODO: test get_prf() behavior in various situations - though overall behavior tested via pbkdf2
+
+ def test_rfc3962(self):
+ "rfc3962 test vectors"
+ self.assertFunctionResults(pbkdf2.pbkdf2, [
+ # result, secret, salt, rounds, keylen, digest="sha1"
+
+ #test case 1 / 128 bit
+ (
+ hb("cdedb5281bb2f801565a1122b2563515"),
+ b("password"), b("ATHENA.MIT.EDUraeburn"), 1, 16
+ ),
+
+ #test case 2 / 128 bit
+ (
+ hb("01dbee7f4a9e243e988b62c73cda935d"),
+ b("password"), b("ATHENA.MIT.EDUraeburn"), 2, 16
+ ),
+
+ #test case 2 / 256 bit
+ (
+ hb("01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86"),
+ b("password"), b("ATHENA.MIT.EDUraeburn"), 2, 32
+ ),
+
+ #test case 3 / 256 bit
+ (
+ hb("5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13"),
+ b("password"), b("ATHENA.MIT.EDUraeburn"), 1200, 32
+ ),
+
+ #test case 4 / 256 bit
+ (
+ hb("d1daa78615f287e6a1c8b120d7062a493f98d203e6be49a6adf4fa574b6e64ee"),
+ b("password"), b('\x12\x34\x56\x78\x78\x56\x34\x12'), 5, 32
+ ),
+
+ #test case 5 / 256 bit
+ (
+ hb("139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1"),
+ b("X"*64), b("pass phrase equals block size"), 1200, 32
+ ),
+
+ #test case 6 / 256 bit
+ (
+ hb("9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a"),
+ b("X"*65), b("pass phrase exceeds block size"), 1200, 32
+ ),
+ ])
+
+ def test_rfc6070(self):
+ "rfc6070 test vectors"
+ self.assertFunctionResults(pbkdf2.pbkdf2, [
+
+ (
+ hb("0c60c80f961f0e71f3a9b524af6012062fe037a6"),
+ b("password"), b("salt"), 1, 20,
+ ),
+
+ (
+ hb("ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"),
+ b("password"), b("salt"), 2, 20,
+ ),
+
+ (
+ hb("4b007901b765489abead49d926f721d065a429c1"),
+ b("password"), b("salt"), 4096, 20,
+ ),
+
+ #just runs too long - could enable if ALL option is set
+ ##(
+ ##
+ ## unhexlify("eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"),
+ ## "password", "salt", 16777216, 20,
+ ##),
+
+ (
+ hb("3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"),
+ b("passwordPASSWORDpassword"),
+ b("saltSALTsaltSALTsaltSALTsaltSALTsalt"),
+ 4096, 25,
+ ),
+
+ (
+ hb("56fa6aa75548099dcc37d7f03425e0c3"),
+ b("pass\00word"), b("sa\00lt"), 4096, 16,
+ ),
+ ])
+
+ def test_invalid_values(self):
+
+ #invalid rounds
+ 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, b('password'), b('salt'),
+ 1, 20*(2**32-1)+1)
+
+ #invalid salt type
+ self.assertRaises(TypeError, pbkdf2.pbkdf2, b('password'), 5, 1, 10)
+
+ #invalid secret type
+ self.assertRaises(TypeError, pbkdf2.pbkdf2, 5, b('salt'), 1, 10)
+
+ #invalid hash
+ 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_default_keylen(self):
+ "test keylen==-1"
+ self.assertEqual(len(pbkdf2.pbkdf2(b('password'), b('salt'), 1, -1,
+ prf='hmac-sha1')), 20)
+
+ self.assertEqual(len(pbkdf2.pbkdf2(b('password'), b('salt'), 1, -1,
+ prf='hmac-sha256')), 32)
+
+ def test_hmac_sha1(self):
+ "test independant hmac_sha1() method"
+ self.assertEqual(
+ 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_sha1_string(self):
+ "test various prf values"
+ self.assertEqual(
+ pbkdf2.pbkdf2(b("secret"), b("salt"), 10, 16, "hmac-sha1"),
+ b('\xe2H\xfbk\x136QF\xf8\xacc\x07\xcc"(\x12')
+ )
+
+ def test_sha512_string(self):
+ "test alternate digest string (sha512)"
+ self.assertFunctionResults(pbkdf2.pbkdf2, [
+ # result, secret, salt, rounds, keylen, digest="sha1"
+
+ #case taken from example in http://grub.enbug.org/Authentication
+ (
+ hb("887CFF169EA8335235D8004242AA7D6187A41E3187DF0CE14E256D85ED97A97357AAA8FF0A3871AB9EEFF458392F462F495487387F685B7472FC6C29E293F0A0"),
+ b("hello"),
+ hb("9290F727ED06C38BA4549EF7DE25CF5642659211B7FC076F2D28FEFD71784BB8D8F6FB244A8CC5C06240631B97008565A120764C0EE9C2CB0073994D79080136"),
+ 10000, 64, "hmac-sha512"
+ ),
+ ])
+
+ def test_sha512_function(self):
+ "test custom digest function"
+ def prf(key, msg):
+ return hmac.new(key, msg, hashlib.sha512).digest()
+
+ self.assertFunctionResults(pbkdf2.pbkdf2, [
+ # result, secret, salt, rounds, keylen, digest="sha1"
+
+ #case taken from example in http://grub.enbug.org/Authentication
+ (
+ hb("887CFF169EA8335235D8004242AA7D6187A41E3187DF0CE14E256D85ED97A97357AAA8FF0A3871AB9EEFF458392F462F495487387F685B7472FC6C29E293F0A0"),
+ b("hello"),
+ hb("9290F727ED06C38BA4549EF7DE25CF5642659211B7FC076F2D28FEFD71784BB8D8F6FB244A8CC5C06240631B97008565A120764C0EE9C2CB0073994D79080136"),
+ 10000, 64, prf,
+ ),
+ ])
+
+has_m2crypto = (pbkdf2._EVP is not None)
+
+if has_m2crypto:
+ class Pbkdf2_M2Crypto_Test(_Pbkdf2BackendTest):
+ descriptionPrefix = "pbkdf2 (m2crypto backend)"
+ enable_m2crypto = True
+
+if not has_m2crypto or enable_option("cover"):
+ class Pbkdf2_Builtin_Test(_Pbkdf2BackendTest):
+ descriptionPrefix = "pbkdf2 (builtin backend)"
+ enable_m2crypto = False
+
+#=========================================================
+#EOF
+#=========================================================
diff --git a/passlib/utils/des.py b/passlib/utils/des.py
index 4172a2e..25f1d0d 100644
--- a/passlib/utils/des.py
+++ b/passlib/utils/des.py
@@ -1,4 +1,5 @@
-"""
+"""passlib.utils.des -- DES block encryption routines
+
History
=======
These routines (which have since been drastically modified for python)
@@ -32,8 +33,7 @@ The copyright & license for that source is as follows::
@version $Id: UnixCrypt2.txt,v 1.1.1.1 2005/09/13 22:20:13 christos Exp $
@author Greg Wilkins (gregw)
-netbsd des-crypt implementation,
-which has some nice notes on how this all works -
+The netbsd des-crypt implementation has some nice notes on how this all works -
http://fxr.googlebit.com/source/lib/libcrypt/crypt.c?v=NETBSD-CURRENT
"""
@@ -45,7 +45,10 @@ which has some nice notes on how this all works -
# core
import struct
# pkg
-from passlib.utils.compat import bytes, join_byte_values, byte_elem_value, irange, irange
+from passlib import exc
+from passlib.utils.compat import bytes, join_byte_values, byte_elem_value, \
+ b, irange, irange, int_types
+from passlib.utils import deprecated_function
# local
__all__ = [
"expand_des_key",
@@ -54,30 +57,29 @@ __all__ = [
]
#=========================================================
-#precalculated iteration ranges & constants
+# constants
#=========================================================
-R8 = irange(8)
-RR8 = irange(7, -1, -1)
-RR4 = irange(3, -1, -1)
-RR12_1 = irange(11, 1, -1)
-RR9_1 = irange(9,-1,-1)
-RR6_S2 = irange(6, -1, -2)
-RR14_S2 = irange(14, -1, -2)
-R16_S2 = irange(0, 16, 2)
+# masks/upper limits for various integer sizes
+INT_24_MASK = 0xffffff
+INT_56_MASK = 0xffffffffffffff
+INT_64_MASK = 0xffffffffffffffff
-INT_24_MAX = 0xffffff
-INT_64_MAX = 0xffffffff
-INT_64_MAX = 0xffffffffffffffff
+# mask to clear parity bits from 64-bit key
+_KDATA_MASK = 0xfefefefefefefefe
+_KPARITY_MASK = 0x0101010101010101
-uint64_struct = struct.Struct(">Q")
+# mask used to setup key schedule
+_KS_MASK = 0xfcfcfcfcffffffff
#=========================================================
-# static tables for des
+# static DES tables
#=========================================================
-PCXROT = IE3264 = SPE = CF6464 = None #placeholders filled in by load_tables
-def load_tables():
+# placeholders filled in by _load_tables()
+PCXROT = IE3264 = SPE = CF6464 = None
+
+def _load_tables():
"delay loading tables until they are actually needed"
global PCXROT, IE3264, SPE, CF6464
@@ -558,10 +560,14 @@ def load_tables():
0x0000000004040000, 0x0000000004040004, 0x0000000004040400, 0x0000000004040404, ),
)
#=========================================================
- #eof load_data
+ # eof _load_tables()
#=========================================================
-def permute(c, p):
+#=========================================================
+# support
+#=========================================================
+
+def _permute(c, p):
"""Returns the permutation of the given 32-bit or 64-bit code with
the specified permutation table."""
#NOTE: only difference between 32 & 64 bit permutations
@@ -573,104 +579,213 @@ def permute(c, p):
return out
#=========================================================
-#des frontend
+# packing & unpacking
+#=========================================================
+_uint64_struct = struct.Struct(">Q")
+
+_BNULL = b('\x00')
+
+def _pack64(value):
+ return _uint64_struct.pack(value)
+
+def _unpack64(value):
+ return _uint64_struct.unpack(value)[0]
+
+def _pack56(value):
+ return _uint64_struct.pack(value)[1:]
+
+def _unpack56(value):
+ return _uint64_struct.unpack(_BNULL+value)[0]
+
+#=========================================================
+# 56->64 key manipulation
#=========================================================
+
+##def expand_7bit(value):
+## "expand 7-bit integer => 7-bits + 1 odd-parity bit"
+## # parity calc adapted from 32-bit even parity alg found at
+## # http://graphics.stanford.edu/~seander/bithacks.html#ParityParallel
+## assert 0 <= value < 0x80, "value out of range"
+## return (value<<1) | (0x9669 >> ((value ^ (value >> 4)) & 0xf)) & 1
+
+_EXPAND_ITER = irange(49,-7,-7)
+
def expand_des_key(key):
- "convert 7 byte des key to 8 byte des key (by adding parity bit every 7 bits)"
- if not isinstance(key, bytes):
- raise TypeError("key must be bytes, not %s" % (type(key),))
+ "convert DES from 7 bytes to 8 bytes (by inserting empty parity bits)"
+ if isinstance(key, bytes):
+ if len(key) != 7:
+ raise ValueError("key must be 7 bytes in size")
+ elif isinstance(key, int_types):
+ if key < 0 or key > INT_56_MASK:
+ raise ValueError("key must be 56-bit non-negative integer")
+ return _unpack64(expand_des_key(_pack56(key)))
+ else:
+ raise exc.ExpectedTypeError(key, "bytes or int", "key")
+ key = _unpack56(key)
+ # NOTE: this function would insert correctly-valued parity bits in each key,
+ # but the parity bit would just be ignored in des_encrypt_block(),
+ # so not bothering to use it.
+ ##return join_byte_values(expand_7bit((key >> shift) & 0x7f)
+ # for shift in _EXPAND_ITER)
+ return join_byte_values(((key>>shift) & 0x7f)<<1 for shift in _EXPAND_ITER)
+
+def shrink_des_key(key):
+ "convert DES key from 8 bytes to 7 bytes (by discarding the parity bits)"
+ if isinstance(key, bytes):
+ if len(key) != 8:
+ raise ValueError("key must be 8 bytes in size")
+ return _pack56(shrink_des_key(_unpack64(key)))
+ elif isinstance(key, int_types):
+ if key < 0 or key > INT_64_MASK:
+ raise ValueError("key must be 64-bit non-negative integer")
+ else:
+ raise exc.ExpectedTypeError(key, "bytes or int", "key")
+ key >>= 1
+ result = 0
+ offset = 0
+ while offset < 56:
+ result |= (key & 0x7f)<<offset
+ key >>= 8
+ offset += 7
+ assert not (result & ~INT_64_MASK)
+ return result
- #NOTE: could probably do this much more cleverly and efficiently,
- # but no need really given it's use.
+#=========================================================
+# des encryption
+#=========================================================
+def des_encrypt_block(key, input, salt=0, rounds=1):
+ """encrypt single block of data using DES, operates on 8-byte strings.
- #NOTE: the parity bits are generally ignored, including by des_encrypt_block below
- assert len(key) == 7
+ :arg key:
+ DES key as 7 byte string, or 8 byte string with parity bits
+ (parity bit values are ignored).
- def iter_bits(source):
- for c in source:
- v = byte_elem_value(c)
- for i in irange(7,-1,-1):
- yield (v>>i) & 1
+ :arg input:
+ plaintext block to encrypt, as 8 byte string.
- out = 0
- p = 1
- for i, b in enumerate(iter_bits(key)):
- out = (out<<1) + b
- p ^= b
- if i % 7 == 6:
- out = (out<<1) + p
- p = 1
-
- return join_byte_values(
- ((out>>s) & 0xFF)
- for s in irange(8*7,-8,-8)
- )
+ :arg salt:
+ optional 24-bit integer used to mutate the base DES algorithm in a
+ manner specific to :class:`~passlib.hash.des_crypt` and it's variants:
+
+ for each bit ``i`` which is set in the salt value,
+ bits ``i`` and ``i+24`` are swapped in the DES E-box output.
+ the default (``salt=0``) provides the normal DES behavior.
-def des_encrypt_block(key, input):
- """do traditional encryption of a single DES block
+ :arg rounds:
+ optional number of rounds of to apply the DES key schedule.
+ the default (``rounds=1``) provides the normal DES behavior,
+ but :class:`~passlib.hash.des_crypt` and it's variants use
+ alternate rounds values.
- :arg key: 8 byte des key
- :arg input: 8 byte plaintext
- :returns: 8 byte ciphertext
+ :raises TypeError: if any of the provided args are of the wrong type.
+ :raises ValueError:
+ if any of the input blocks are the wrong size,
+ or the salt/rounds values are out of range.
- all values must be :class:`bytes`
+ :returns:
+ resulting 8-byte ciphertext block.
"""
- if not isinstance(key, bytes):
- raise TypeError("key must be bytes, not %s" % (type(key),))
- if len(key) == 7:
- key = expand_des_key(key)
- if not isinstance(input, bytes):
- raise TypeError("input must be bytes, not %s" % (type(input),))
- input = uint64_struct.unpack(input)[0]
- key = uint64_struct.unpack(key)[0]
- out = mdes_encrypt_int_block(key, input, 0, 1)
- return uint64_struct.pack(out)
-
-def mdes_encrypt_int_block(key, input, salt=0, rounds=1):
- """do modified multi-round DES encryption of single DES block.
-
- the function implements the salted, variable-round version
- of DES used by :class:`~passlib.hash.des_crypt` and related variants.
- it also can perform regular DES encryption
- by using ``salt=0, rounds=1`` (the default values).
-
- :arg key: 8 byte des key as integer
- :arg input: 8 byte plaintext block as integer
- :arg salt: integer 24 bit salt, used to mutate output (defaults to 0)
- :arg rounds: number of rounds of DES encryption to apply (defaults to 1)
-
- The salt is used to to mutate the normal DES encrypt operation
- by swapping bits ``i`` and ``i+24`` in the DES E-Box output
- if and only if bit ``i`` is set in the salt value. Thus,
- if the salt is set to ``0``, normal DES encryption is performed.
+ # validate & unpack key
+ if isinstance(key, bytes):
+ if len(key) == 7:
+ key = expand_des_key(key)
+ elif len(key) != 8:
+ raise ValueError("key must be 7 or 8 bytes")
+ key = _unpack64(key)
+ else:
+ raise exc.ExpectedTypeError(key, "bytes", "key")
+
+ # validate & unpack input
+ if isinstance(input, bytes):
+ if len(input) != 8:
+ raise ValueError("input block must be 8 bytes")
+ input = _unpack64(input)
+ else:
+ raise exc.ExpectedTypeError(input, "bytes", "input")
+
+ # hand things off to other func
+ result = des_encrypt_int_block(key, input, salt, rounds)
+
+ # repack result
+ return _pack64(result)
+
+def des_encrypt_int_block(key, input, salt=0, rounds=1):
+ """encrypt single block of data using DES, operates on 64-bit integers.
+
+ this function is essentially the same as :func:`des_encrypt_block`,
+ except that it operates on integers, and will NOT automatically
+ expand 56-bit keys if provided (since there's no way to detect them).
+
+ :arg key:
+ DES key as 64-bit integer (the parity bits are ignored).
+
+ :arg input:
+ input block as 64-bit integer
+
+ :arg salt:
+ optional 24-bit integer used to mutate the base DES algorithm.
+ defaults to ``0`` (no mutation applied).
+
+ :arg rounds:
+ optional number of rounds of to apply the DES key schedule.
+ defaults to ``1``.
+
+ :raises TypeError: if any of the provided args are of the wrong type.
+ :raises ValueError:
+ if any of the input blocks are the wrong size,
+ or the salt/rounds values are out of range.
:returns:
- resulting block as 8 byte integer
+ resulting ciphertext as 64-bit integer.
"""
+ #-------------------------------------------------------------------
+ # input validation
+ #-------------------------------------------------------------------
+
+ # validate salt, rounds
+ if rounds < 1:
+ raise ValueError("rounds must be positive integer")
+ if salt < 0 or salt > INT_24_MASK:
+ raise ValueError("salt must be 24-bit non-negative integer")
+
+ # validate & unpack key
+ if not isinstance(key, int_types):
+ raise exc.ExpectedTypeError(key, "int", "key")
+ elif key < 0 or key > INT_64_MASK:
+ raise ValueError("key must be 64-bit non-negative integer")
+
+ # validate & unpack input
+ if not isinstance(input, int_types):
+ raise exc.ExpectedTypeError(input, "int", "input")
+ elif input < 0 or input > INT_64_MASK:
+ raise ValueError("input must be 64-bit non-negative integer")
+
+ #-------------------------------------------------------------------
+ # DES setup
+ #-------------------------------------------------------------------
+ # load tables if not already done
global SPE, PCXROT, IE3264, CF6464
+ if PCXROT is None:
+ _load_tables()
- #bounds check
- assert 0 <= input <= INT_64_MAX, "input value out of range"
- assert 0 <= salt <= INT_24_MAX, "salt value out of range"
- assert rounds >= 0, "rounds out of range"
- assert 0 <= key <= INT_64_MAX, "key value out of range"
+ # load SPE into local vars to speed things up and remove an array access call
+ SPE0, SPE1, SPE2, SPE3, SPE4, SPE5, SPE6, SPE7 = SPE
- #load tables if not already done
- if PCXROT is None:
- load_tables()
+ # NOTE: parity bits are ignored completely
+ # (UTs do fuzz testing to ensure this)
- #convert key int -> key schedule
- #NOTE: generation was modified to output two elements at a time,
- #to optimize for per-round algorithm below.
- mask = ~0x0303030300000000
- def _gen(K):
+ # generate key schedule
+ # NOTE: generation was modified to output two elements at a time,
+ # so that per-round loop could do two passes at once.
+ def _iter_key_schedule(ks_odd):
+ "given 64-bit key, iterates over the 8 (even,odd) key schedule pairs"
for p_even, p_odd in PCXROT:
- K1 = permute(K, p_even)
- K = permute(K1, p_odd)
- yield K1 & mask, K & mask
- ks_list = list(_gen(key))
+ ks_even = _permute(ks_odd, p_even)
+ ks_odd = _permute(ks_even, p_odd)
+ yield ks_even & _KS_MASK, ks_odd & _KS_MASK
+ ks_list = list(_iter_key_schedule(key))
- #expand 24 bit salt -> 32 bit
+ # expand 24 bit salt -> 32 bit per des_crypt & bsdi_crypt
salt = (
((salt & 0x00003f) << 26) |
((salt & 0x000fc0) << 12) |
@@ -678,26 +793,25 @@ def mdes_encrypt_int_block(key, input, salt=0, rounds=1):
((salt & 0xfc0000) >> 16)
)
- #init L & R
+ # init L & R
if input == 0:
L = R = 0
else:
L = ((input >> 31) & 0xaaaaaaaa) | (input & 0x55555555)
- L = permute(L, IE3264)
+ L = _permute(L, IE3264)
R = ((input >> 32) & 0xaaaaaaaa) | ((input >> 1) & 0x55555555)
- R = permute(R, IE3264)
-
- #load SPE into local vars to speed things up and remove an array access call
- SPE0, SPE1, SPE2, SPE3, SPE4, SPE5, SPE6, SPE7 = SPE
+ R = _permute(R, IE3264)
- #run specified number of passed
+ #-------------------------------------------------------------------
+ # main DES loop - run for specified number of rounds
+ #-------------------------------------------------------------------
while rounds:
rounds -= 1
- #run over each part of the schedule, 2 parts at a time
+ # run over each part of the schedule, 2 parts at a time
for ks_even, ks_odd in ks_list:
- k = ((R>>32) ^ R) & salt #use the salt to alter specific bits
+ k = ((R>>32) ^ R) & salt # use the salt to flip specific bits
B = (k<<32) ^ k ^ R ^ ks_even
L ^= (SPE0[(B>>58)&0x3f] ^ SPE1[(B>>50)&0x3f] ^
@@ -705,7 +819,7 @@ def mdes_encrypt_int_block(key, input, salt=0, rounds=1):
SPE4[(B>>26)&0x3f] ^ SPE5[(B>>18)&0x3f] ^
SPE6[(B>>10)&0x3f] ^ SPE7[(B>>2)&0x3f])
- k = ((L>>32) ^ L) & salt #use the salt to alter specific bits
+ k = ((L>>32) ^ L) & salt # use the salt to flip specific bits
B = (k<<32) ^ k ^ L ^ ks_odd
R ^= (SPE0[(B>>58)&0x3f] ^ SPE1[(B>>50)&0x3f] ^
@@ -716,6 +830,9 @@ def mdes_encrypt_int_block(key, input, salt=0, rounds=1):
# swap L and R
L, R = R, L
+ #-------------------------------------------------------------------
+ # return final result
+ #-------------------------------------------------------------------
C = (
((L>>3) & 0x0f0f0f0f00000000)
|
@@ -725,10 +842,16 @@ def mdes_encrypt_int_block(key, input, salt=0, rounds=1):
|
((R<<1) & 0x00000000f0f0f0f0)
)
-
- C = permute(C, CF6464)
-
- return C
+ return _permute(C, CF6464)
+
+def mdes_encrypt_int_block(key, input, salt=0, rounds=1): # pragma: no cover
+ warn("mdes_encrypt_int_block() has been deprecated as of Passlib 1.6,"
+ "and will be removed in Passlib 1.8, use des_encrypt_int_block instead.")
+ if isinstance(key, bytes):
+ if len(key) == 7:
+ key = expand_des_key(key)
+ key = _unpack64(key)
+ return des_encrypt_int_block(key, input, salt, rounds)
#=========================================================
#eof