def test_custom_prf(self): """test custom prf function""" from passlib.utils.pbkdf2 import pbkdf2 def prf(key, msg): return hashlib.md5(key+msg+b('fooey')).digest() result = pbkdf2(b('secret'), b('salt'), 1000, 20, prf) self.assertEqual(result, hb('5fe7ce9f7e379d3f65cbc66ba8aa6440474a6849'))
def test_is_ascii_safe(self): "test is_ascii_safe()" from passlib.utils import is_ascii_safe self.assertTrue(is_ascii_safe(b("\x00abc\x7f"))) self.assertTrue(is_ascii_safe(u("\x00abc\x7f"))) self.assertFalse(is_ascii_safe(b("\x00abc\x80"))) self.assertFalse(is_ascii_safe(u("\x00abc\x80")))
def test_00_constructor_autoload(self): "test constructor autoload" # check with existing file path = self.mktemp() set_file(path, self.sample_01) ht = apache.HtpasswdFile(path) self.assertEqual(ht.to_string(), self.sample_01) self.assertEqual(ht.path, path) self.assertTrue(ht.mtime) # check changing path ht.path = path + "x" self.assertEqual(ht.path, path + "x") self.assertFalse(ht.mtime) # check new=True ht = apache.HtpasswdFile(path, new=True) self.assertEqual(ht.to_string(), b("")) self.assertEqual(ht.path, path) self.assertFalse(ht.mtime) # check autoload=False (deprecated alias for new=True) with self.assertWarningList("``autoload=False`` is deprecated"): ht = apache.HtpasswdFile(path, autoload=False) self.assertEqual(ht.to_string(), b("")) self.assertEqual(ht.path, path) self.assertFalse(ht.mtime) # check missing file os.remove(path) self.assertRaises(IOError, apache.HtpasswdFile, path)
def test_11_norm_checksum(self): "test GenericHandler checksum handling" # setup helpers class d1(uh.GenericHandler): name = 'd1' checksum_size = 4 checksum_chars = u('xz') _stub_checksum = u('z')*4 def norm_checksum(*a, **k): return d1(*a, **k).checksum # too small self.assertRaises(ValueError, norm_checksum, u('xxx')) # right size self.assertEqual(norm_checksum(u('xxxx')), u('xxxx')) self.assertEqual(norm_checksum(u('xzxz')), u('xzxz')) # too large self.assertRaises(ValueError, norm_checksum, u('xxxxx')) # wrong chars self.assertRaises(ValueError, norm_checksum, u('xxyx')) # wrong type self.assertRaises(TypeError, norm_checksum, b('xxyx')) # relaxed with self.assertWarningList("checksum should be unicode"): self.assertEqual(norm_checksum(b('xxzx'), relaxed=True), u('xxzx')) self.assertRaises(TypeError, norm_checksum, 1, relaxed=True) # test _stub_checksum behavior self.assertIs(norm_checksum(u('zzzz')), None)
def test_04_encrypt_ints(self): """test des_encrypt_int_block()""" from passlib.utils.des import des_encrypt_int_block # 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)
def test_to_native_str(self): "test to_native_str()" from passlib.utils import to_native_str # test plain ascii self.assertEqual(to_native_str(u('abc'), 'ascii'), 'abc') self.assertEqual(to_native_str(b('abc'), 'ascii'), 'abc') # test invalid ascii if PY3: self.assertEqual(to_native_str(u('\xE0'), 'ascii'), '\xE0') self.assertRaises(UnicodeDecodeError, to_native_str, b('\xC3\xA0'), 'ascii') else: self.assertRaises(UnicodeEncodeError, to_native_str, u('\xE0'), 'ascii') self.assertEqual(to_native_str(b('\xC3\xA0'), 'ascii'), '\xC3\xA0') # test latin-1 self.assertEqual(to_native_str(u('\xE0'), 'latin-1'), '\xE0') self.assertEqual(to_native_str(b('\xE0'), 'latin-1'), '\xE0') # test utf-8 self.assertEqual(to_native_str(u('\xE0'), 'utf-8'), '\xE0' if PY3 else '\xC3\xA0') self.assertEqual(to_native_str(b('\xC3\xA0'), 'utf-8'), '\xE0' if PY3 else '\xC3\xA0') # other types rejected self.assertRaises(TypeError, to_native_str, None, 'ascii')
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)
def test_05_load(self): "test load()" # setup empty file path = self.mktemp() set_file(path, "") backdate_file_mtime(path, 5) ha = apache.HtpasswdFile(path, default_scheme="plaintext") self.assertEqual(ha.to_string(), b("")) # make changes, check load_if_changed() does nothing ha.set_password("user1", "pass1") ha.load_if_changed() self.assertEqual(ha.to_string(), b("user1:pass1\n")) # change file set_file(path, self.sample_01) ha.load_if_changed() self.assertEqual(ha.to_string(), self.sample_01) # make changes, check load() overwrites them ha.set_password("user5", "pass5") ha.load() self.assertEqual(ha.to_string(), self.sample_01) # test load w/ no path hb = apache.HtpasswdFile() self.assertRaises(RuntimeError, hb.load) self.assertRaises(RuntimeError, hb.load_if_changed) # test load w/ dups and explicit path set_file(path, self.sample_dup) hc = apache.HtpasswdFile() hc.load(path) self.assertTrue(hc.check_password('user1','pass1'))
def test_08_get_hash(self): "test get_hash()" ht = apache.HtpasswdFile.from_string(self.sample_01) self.assertEqual(ht.get_hash("user3"), b("{SHA}3ipNV1GrBtxPmHFC21fCbVCSXIo=")) self.assertEqual(ht.get_hash("user4"), b("pass4")) self.assertEqual(ht.get_hash("user5"), None) with self.assertWarningList("find\(\) is deprecated"): self.assertEqual(ht.find("user4"), b("pass4"))
def get_prf(name): """lookup pseudo-random family (prf) by name. :arg name: this must be the name of a recognized prf. currently this only recognizes names with the format :samp:`hmac-{digest}`, where :samp:`{digest}` is the name of a hash function such as ``md5``, ``sha256``, etc. this can also be a callable with the signature ``prf(secret, message) -> digest``, in which case it will be returned unchanged. :raises ValueError: if the name is not known :raises TypeError: if the name is not a callable or string :returns: a tuple of :samp:`({func}, {digest_size})`. * :samp:`{func}` is a function implementing the specified prf, and has the signature ``func(secret, message) -> digest``. * :samp:`{digest_size}` is an integer indicating the number of bytes the function returns. usage example:: >>> from passlib.utils.pbkdf2 import get_prf >>> hmac_sha256, dsize = get_prf("hmac-sha256") >>> hmac_sha256 <function hmac_sha256 at 0x1e37c80> >>> dsize 32 >>> digest = hmac_sha256('password', 'message') this function will attempt to return the fastest implementation it can find; if M2Crypto is present, and supports the specified prf, :func:`M2Crypto.EVP.hmac` will be used behind the scenes. """ global _prf_cache if name in _prf_cache: return _prf_cache[name] if isinstance(name, str): if name.startswith("hmac-") or name.startswith("hmac_"): retval = _get_hmac_prf(name[5:]) else: raise ValueError("unknown prf algorithm: %r" % (name,)) elif callable(name): # assume it's a callable, use it directly digest_size = len(name(b('x'),b('y'))) retval = (name, digest_size) else: raise ExpectedTypeError(name, "str or callable", "prf name") _prf_cache[name] = retval return retval
def test_crypt(self): "test crypt.crypt() wrappers" from passlib.utils import has_crypt, safe_crypt, test_crypt # test everything is disabled if not has_crypt: self.assertEqual(safe_crypt("test", "aa"), None) self.assertFalse(test_crypt("test", "aaqPiZY5xR5l.")) raise self.skipTest("crypt.crypt() not available") # XXX: this assumes *every* crypt() implementation supports des_crypt. # if this fails for some platform, this test will need modifying. # test return type self.assertIsInstance(safe_crypt(u("test"), u("aa")), unicode) # test ascii password h1 = u('aaqPiZY5xR5l.') self.assertEqual(safe_crypt(u('test'), u('aa')), h1) self.assertEqual(safe_crypt(b('test'), b('aa')), h1) # test utf-8 / unicode password h2 = u('aahWwbrUsKZk.') self.assertEqual(safe_crypt(u('test\u1234'), 'aa'), h2) self.assertEqual(safe_crypt(b('test\xe1\x88\xb4'), 'aa'), h2) # test latin-1 password hash = safe_crypt(b('test\xff'), 'aa') if PY3: # py3 supports utf-8 bytes only. self.assertEqual(hash, None) else: # but py2 is fine. self.assertEqual(hash, u('aaOx.5nbTU/.M')) # test rejects null chars in password self.assertRaises(ValueError, safe_crypt, '\x00', 'aa') # check test_crypt() h1x = h1[:-1] + 'x' self.assertTrue(test_crypt("test", h1)) self.assertFalse(test_crypt("test", h1x)) # check crypt returning variant error indicators # some platforms return None on errors, others empty string, # The BSDs in some cases return ":" import passlib.utils as mod orig = mod._crypt try: fake = None mod._crypt = lambda secret, hash: fake for fake in [None, "", ":", ":0", "*0"]: self.assertEqual(safe_crypt("test", "aa"), None) self.assertFalse(test_crypt("test", h1)) fake = 'xxx' self.assertEqual(safe_crypt("test", "aa"), "xxx") finally: mod._crypt = orig
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_91_parsehash(self): "test parsehash()" # NOTE: this just tests some existing GenericHandler classes from passlib import hash # # parsehash() # # simple hash w/ salt result = hash.des_crypt.parsehash("OgAwTx2l6NADI") self.assertEqual(result, {'checksum': u('AwTx2l6NADI'), 'salt': u('Og')}) # parse rounds and extra implicit_rounds flag h = '$5$LKO/Ute40T3FNF95$U0prpBQd4PloSGU0pnpM4z9wKn4vZ1.jsrzQfPqxph9' s = u('LKO/Ute40T3FNF95') c = u('U0prpBQd4PloSGU0pnpM4z9wKn4vZ1.jsrzQfPqxph9') result = hash.sha256_crypt.parsehash(h) self.assertEqual(result, dict(salt=s, rounds=5000, implicit_rounds=True, checksum=c)) # omit checksum result = hash.sha256_crypt.parsehash(h, checksum=False) self.assertEqual(result, dict(salt=s, rounds=5000, implicit_rounds=True)) # sanitize result = hash.sha256_crypt.parsehash(h, sanitize=True) self.assertEqual(result, dict(rounds=5000, implicit_rounds=True, salt=u('LK**************'), checksum=u('U0pr***************************************'))) # parse w/o implicit rounds flag result = hash.sha256_crypt.parsehash('$5$rounds=10428$uy/jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3') self.assertEqual(result, dict( checksum=u('YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3'), salt=u('uy/jIAhCetNCTtb0'), rounds=10428, )) # parsing of raw checksums & salts h1 = '$pbkdf2$60000$DoEwpvQeA8B4T.k951yLUQ$O26Y3/NJEiLCVaOVPxGXshyjW8k' result = hash.pbkdf2_sha1.parsehash(h1) self.assertEqual(result, dict( checksum=b(';n\x98\xdf\xf3I\x12"\xc2U\xa3\x95?\x11\x97\xb2\x1c\xa3[\xc9'), rounds=60000, salt=b('\x0e\x810\xa6\xf4\x1e\x03\xc0xO\xe9=\xe7\\\x8bQ'), )) # sanitizing of raw checksums & salts result = hash.pbkdf2_sha1.parsehash(h1, sanitize=True) self.assertEqual(result, dict( checksum=u('O26************************'), rounds=60000, salt=u('Do********************'), ))
def test_md4_copy(self): "test md4 copy()" from passlib.utils.md4 import md4 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')
def test_md4_copy(self): """test md4 copy()""" from passlib.utils.md4 import md4 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')
def _get_hmac_prf(digest): "helper to return HMAC prf for specific digest" def tag_wrapper(prf): prf.__name__ = "hmac_" + digest prf.__doc__ = ("hmac_%s(key, msg) -> digest;" " generated by passlib.utils.pbkdf2.get_prf()" % digest) 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 # don't expect to ever get here, but will fall back to pure-python if we do. warn("M2Crypto.EVP.HMAC() returned unexpected result " # pragma: no cover -- sanity check "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) except ValueError: pass else: # it does. so use M2Crypto's hmac & digest code hmac_const = _EVP.hmac def prf(key, msg): return hmac_const(key, msg, digest) digest_size = len(result) tag_wrapper(prf) return prf, digest_size # fall back to hashlib-based implementation digest_const = getattr(hashlib, digest, None) if not digest_const: raise ValueError("unknown hash algorithm: %r" % (digest,)) tmp = digest_const() block_size = tmp.block_size assert block_size >= 16, "unacceptably low block size" digest_size = tmp.digest_size del tmp def prf(key, msg): # simplified version of stdlib's hmac module 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() tag_wrapper(prf) return prf, digest_size
def test_01_delete_autosave(self): path = self.mktemp() sample = b('user1:pass1\nuser2:pass2\n') set_file(path, sample) ht = apache.HtpasswdFile(path) ht.delete("user1") self.assertEqual(get_file(path), sample) ht = apache.HtpasswdFile(path, autosave=True) ht.delete("user1") self.assertEqual(get_file(path), b("user2:pass2\n"))
def test_02_set_password_autosave(self): path = self.mktemp() sample = b('user1:pass1\n') set_file(path, sample) ht = apache.HtpasswdFile(path) ht.set_password("user1", "pass2") self.assertEqual(get_file(path), sample) ht = apache.HtpasswdFile(path, default_scheme="plaintext", autosave=True) ht.set_password("user1", "pass2") self.assertEqual(get_file(path), b("user1:pass2\n"))
def test_md4_copy(self): "test md4 copy()" from passlib.utils.md4 import md4 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")
def test_bytes(self): "test b() helper, bytes and native str type" if PY3: import builtins self.assertIs(bytes, builtins.bytes) else: import __builtin__ as builtins self.assertIs(bytes, builtins.str) self.assertIsInstance(b(''), bytes) self.assertIsInstance(b('\x00\xff'), bytes) if PY3: self.assertEqual(b('\x00\xff').decode("latin-1"), "\x00\xff") else: self.assertEqual(b('\x00\xff'), "\x00\xff")
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))
def test_md4_update(self): """test md4 update""" from passlib.utils.md4 import md4 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 or self._disable_native: 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_update(self): "test md4 update" from passlib.utils.md4 import md4 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 or self._disable_native: 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_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))
def test_decode_bytes_padding(self): "test decode_bytes() ignores padding bits" bchr = (lambda v: bytes([v])) if PY3 else chr engine = self.engine m = self.m decode = engine.decode_bytes BNULL = b("\x00") # length == 2 mod 4: 4 bits of padding self.assertEqual(decode(m(0,0)), BNULL) for i in range(0,6): if engine.big: # 4 lsb padding correct = BNULL if i < 4 else bchr(1<<(i-4)) else: # 4 msb padding correct = bchr(1<<(i+6)) if i < 2 else BNULL self.assertEqual(decode(m(0,1<<i)), correct, "%d/4 bits:" % i) # length == 3 mod 4: 2 bits of padding self.assertEqual(decode(m(0,0,0)), BNULL*2) for i in range(0,6): if engine.big: # 2 lsb are padding correct = BNULL if i < 2 else bchr(1<<(i-2)) else: # 2 msg are padding correct = bchr(1<<(i+4)) if i < 4 else BNULL self.assertEqual(decode(m(0,0,1<<i)), BNULL + correct, "%d/2 bits:" % i)
def test_decode_bytes_padding(self): "test decode_bytes() ignores padding bits" bchr = (lambda v: bytes([v])) if PY3 else chr engine = self.engine m = self.m decode = engine.decode_bytes BNULL = b("\x00") # length == 2 mod 4: 4 bits of padding self.assertEqual(decode(m(0, 0)), BNULL) for i in range(0, 6): if engine.big: # 4 lsb padding correct = BNULL if i < 4 else bchr(1 << (i - 4)) else: # 4 msb padding correct = bchr(1 << (i + 6)) if i < 2 else BNULL self.assertEqual(decode(m(0, 1 << i)), correct, "%d/4 bits:" % i) # length == 3 mod 4: 2 bits of padding self.assertEqual(decode(m(0, 0, 0)), BNULL * 2) for i in range(0, 6): if engine.big: # 2 lsb are padding correct = BNULL if i < 2 else bchr(1 << (i - 2)) else: # 2 msg are padding correct = bchr(1 << (i + 4)) if i < 4 else BNULL self.assertEqual(decode(m(0, 0, 1 << i)), BNULL + correct, "%d/2 bits:" % i)
class mssql2005(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): """This class implements the password hash used by MS-SQL 2005, and follows the :ref:`password-hash-api`. It supports a fixed-length salt. The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords: :type salt: bytes :param salt: Optional salt string. If not specified, one will be autogenerated (this is recommended). If specified, it must be 4 bytes in length. :type relaxed: bool :param relaxed: By default, providing an invalid value for one of the other keywords will result in a :exc:`ValueError`. If ``relaxed=True``, and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning` will be issued instead. Correctable errors include ``salt`` strings that are too long. """ #=================================================================== # algorithm information #=================================================================== name = "mssql2005" setting_kwds = ("salt", ) checksum_size = 20 min_salt_size = max_salt_size = 4 _stub_checksum = b("\x00") * 20 #=================================================================== # formatting #=================================================================== # 0x0100 - 2 byte identifier # 4 byte salt # 20 byte checksum # = 26 bytes # encoded '0x' + 52 chars = 54 @classmethod def identify(cls, hash): return _ident_mssql(hash, 54, 26) @classmethod def from_string(cls, hash): data = _parse_mssql(hash, 54, 26, cls) return cls(salt=data[:4], checksum=data[4:]) def to_string(self): raw = self.salt + (self.checksum or self._stub_checksum) # raw bytes format - BIDENT2 + raw return "0x0100" + bascii_to_str(hexlify(raw)).upper() def _calc_checksum(self, secret): if isinstance(secret, bytes): secret = secret.decode("utf-8") return _raw_mssql(secret, self.salt)
def genhash(cls, secret, hash): if secret is None: raise TypeError("no secret provided") if isinstance(secret, unicode): secret = secret.encode("utf-8") if hash is not None and not cls.identify(hash): raise ValueError("invalid hash") return hashlib.sha1(b("xyz") + secret).hexdigest()
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)
class atlassian_pbkdf2_sha1(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): """This class implements the PBKDF2 hash used by Atlassian. It supports a fixed-length salt, and a fixed number of rounds. The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keyword: :type salt: bytes :param salt: Optional salt bytes. If specified, the length must be exactly 16 bytes. If not specified, a salt will be autogenerated (this is recommended). :type relaxed: bool :param relaxed: By default, providing an invalid value for one of the other keywords will result in a :exc:`ValueError`. If ``relaxed=True``, and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning` will be issued instead. Correctable errors include ``salt`` strings that are too long. .. versionadded:: 1.6 """ #--GenericHandler-- name = "atlassian_pbkdf2_sha1" setting_kwds = ("salt", ) ident = u("{PKCS5S2}") checksum_size = 32 _stub_checksum = b("\x00") * 32 #--HasRawSalt-- min_salt_size = max_salt_size = 16 @classmethod def from_string(cls, hash): hash = to_unicode(hash, "ascii", "hash") ident = cls.ident if not hash.startswith(ident): raise uh.exc.InvalidHashError(cls) data = b64decode(hash[len(ident):].encode("ascii")) salt, chk = data[:16], data[16:] return cls(salt=salt, checksum=chk) def to_string(self): data = self.salt + (self.checksum or self._stub_checksum) hash = self.ident + b64encode(data).decode("ascii") return uascii_to_str(hash) def _calc_checksum(self, secret): # TODO: find out what crowd's policy is re: unicode if isinstance(secret, unicode): secret = secret.encode("utf-8") # crowd seems to use a fixed number of rounds. return pbkdf2(secret, self.salt, 10000, 32, "hmac-sha1")
def test_07_realms(self): "test realms() & delete_realm()" ht = apache.HtdigestFile.from_string(self.sample_01) self.assertEqual(ht.delete_realm("x"), 0) self.assertEqual(ht.realms(), ['realm']) self.assertEqual(ht.delete_realm("realm"), 4) self.assertEqual(ht.realms(), []) self.assertEqual(ht.to_string(), b(""))
def test_09_to_string(self): "test to_string" # check with known sample ht = apache.HtpasswdFile.from_string(self.sample_01) self.assertEqual(ht.to_string(), self.sample_01) # test blank ht = apache.HtpasswdFile() self.assertEqual(ht.to_string(), b(""))
def test_10_to_string(self): "test to_string()" # check sample ht = apache.HtdigestFile.from_string(self.sample_01) self.assertEqual(ht.to_string(), self.sample_01) # check blank ht = apache.HtdigestFile() self.assertEqual(ht.to_string(), b(""))
def test_12_norm_checksum_raw(self): "test GenericHandler + HasRawChecksum mixin" class d1(uh.HasRawChecksum, uh.GenericHandler): name = 'd1' checksum_size = 4 _stub_checksum = b('0')*4 def norm_checksum(*a, **k): return d1(*a, **k).checksum # test bytes self.assertEqual(norm_checksum(b('1234')), b('1234')) # test unicode self.assertRaises(TypeError, norm_checksum, u('xxyx')) self.assertRaises(TypeError, norm_checksum, u('xxyx'), relaxed=True) # test _stub_checksum behavior self.assertIs(norm_checksum(b('0')*4), None)
def test_to_unicode(self): "test to_unicode()" from passlib.utils import to_unicode # check unicode inputs self.assertEqual(to_unicode(u('abc')), u('abc')) self.assertEqual(to_unicode(u('\x00\xff')), u('\x00\xff')) # check unicode input ignores encoding self.assertEqual(to_unicode(u('\x00\xff'), "ascii"), u('\x00\xff')) # check bytes input self.assertEqual(to_unicode(b('abc')), u('abc')) self.assertEqual(to_unicode(b('\x00\xc3\xbf')), u('\x00\xff')) self.assertEqual(to_unicode(b('\x00\xff'), 'latin-1'), u('\x00\xff')) self.assertRaises(ValueError, to_unicode, b('\x00\xff')) # check other self.assertRaises(AssertionError, to_unicode, 'abc', None) self.assertRaises(TypeError, to_unicode, None)
def test_05_load(self): "test load()" # setup empty file path = self.mktemp() set_file(path, "") backdate_file_mtime(path, 5) ha = apache.HtdigestFile(path) self.assertEqual(ha.to_string(), b("")) # make changes, check load_if_changed() does nothing ha.set_password("user1", "realm", "pass1") ha.load_if_changed() self.assertEqual(ha.to_string(), b('user1:realm:2a6cf53e7d8f8cf39d946dc880b14128\n')) # change file set_file(path, self.sample_01) ha.load_if_changed() self.assertEqual(ha.to_string(), self.sample_01) # make changes, check load_if_changed overwrites them ha.set_password("user5", "realm", "pass5") ha.load() self.assertEqual(ha.to_string(), self.sample_01) # test load w/ no path hb = apache.HtdigestFile() self.assertRaises(RuntimeError, hb.load) self.assertRaises(RuntimeError, hb.load_if_changed) # test load w/ explicit path hc = apache.HtdigestFile() hc.load(path) self.assertEqual(hc.to_string(), self.sample_01) # change file, test deprecated force=False kwd ensure_mtime_changed(path) set_file(path, "") with self.assertWarningList(r"load\(force=False\) is deprecated"): ha.load(force=False) self.assertEqual(ha.to_string(), b(""))
def raw_lmhash(secret, encoding="ascii", hex=False): "encode password using des-based LMHASH algorithm; returns string of raw bytes, or unicode hex" #NOTE: various references say LMHASH uses the OEM codepage of the host # for it's encoding. until a clear reference is found, # as well as a path for getting the encoding, # letting this default to "ascii" to prevent incorrect hashes # from being made w/o user explicitly choosing an encoding. if isinstance(secret, unicode): secret = secret.encode(encoding) ns = secret.upper()[:14] + b("\x00") * (14-len(secret)) out = des_encrypt_block(ns[:7], LM_MAGIC) + des_encrypt_block(ns[7:], LM_MAGIC) return hexlify(out).decode("ascii") if hex else out
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 des_cbc_encrypt(key, value, iv=b('\x00') * 8, pad=b('\x00')): """performs des-cbc encryption, returns only last block. this performs a specific DES-CBC encryption implementation as needed by the Oracle10 hash. it probably won't be useful for other purposes as-is. input value is null-padded to multiple of 8 bytes. :arg key: des key as bytes :arg value: value to encrypt, as bytes. :param iv: optional IV :param pad: optional pad byte :returns: last block of DES-CBC encryption of all ``value``'s byte blocks. """ value += pad * (-len(value) % 8) # null pad to multiple of 8 hash = iv # start things off for offset in irange(0, len(value), 8): chunk = xor_bytes(hash, value[offset:offset + 8]) hash = des_encrypt_block(key, chunk) return hash