def test_is_ascii_safe(self): """test is_ascii_safe()""" from lib.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_to_native_str(self): """test to_native_str()""" from lib.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_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_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_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 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 lib.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_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 test_03_encrypt_bytes(self): """test des_encrypt_block()""" from lib.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_crypt(self): """test crypt.crypt() wrappers""" from lib.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 lib.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_custom_prf(self): """test custom prf function""" from lib.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_91_parsehash(self): """test parsehash()""" # NOTE: this just tests some existing GenericHandler classes from lib.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 lib.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_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 _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 its 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_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_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_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_md4_update(self): """test md4 update""" from lib.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 lib.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 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()
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_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_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_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_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 lib.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 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 its 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_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 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
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 its 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_getrandstr(self): """test getrandstr()""" from lib.passlib.utils import getrandstr, rng def f(*a, **k): return getrandstr(rng, *a, **k) # count 0 self.assertEqual(f('abc', 0), '') # count <0 self.assertRaises(ValueError, f, 'abc', -1) # letters 0 self.assertRaises(ValueError, f, '', 0) # letters 1 self.assertEqual(f('a', 5), 'aaaaa') # letters x = f(u('abc'), 16) y = f(u('abc'), 16) self.assertIsInstance(x, unicode) self.assertNotEqual(x, y) self.assertEqual(sorted(set(x)), [u('a'), u('b'), u('c')]) # bytes x = f(b('abc'), 16) y = f(b('abc'), 16) self.assertIsInstance(x, bytes) self.assertNotEqual(x, y) # NOTE: decoding this due to py3 bytes self.assertEqual(sorted(set(x.decode("ascii"))), [u('a'), u('b'), u('c')]) # generate_password from lib.passlib.utils import generate_password self.assertEqual(len(generate_password(15)), 15)
def digest(self): # NOTE: backing up state so we can restore it after _process is called, # in case object is updated again (this is only attr altered by this method) orig = list(self._state) # final block: buf + 0x80, # then 0x00 padding until congruent w/ 56 mod 64 bytes # then last 8 bytes = msg length in bits buf = self._buf msglen = self._count*512 + len(buf)*8 block = buf + b('\x80') + b('\x00') * ((119-len(buf)) % 64) + \ struct.pack("<2I", msglen & MASK_32, (msglen>>32) & MASK_32) if len(block) == 128: self._process(block[:64]) self._process(block[64:]) else: assert len(block) == 64 self._process(block) # render digest & restore un-finalized state out = struct.pack("<4I", *self._state) self._state = orig return out
def _calc_checksum(self, secret): # FIXME: no idea if mysql has a policy about handling unicode passwords if isinstance(secret, unicode): secret = secret.encode("utf-8") MASK_32 = 0xffffffff MASK_31 = 0x7fffffff WHITE = b(' \t') nr1 = 0x50305735 nr2 = 0x12345671 add = 7 for c in secret: if c in WHITE: continue tmp = byte_elem_value(c) nr1 ^= ((((nr1 & 63)+add)*tmp) + (nr1 << 8)) & MASK_32 nr2 = (nr2+((nr2 << 8) ^ nr1)) & MASK_32 add = (add+tmp) & MASK_32 return u("%08x%08x") % (nr1 & MASK_31, nr2 & MASK_31)
def test_00_static_handler(self): """test StaticHandler class""" class d1(uh.StaticHandler): name = "d1" context_kwds = ("flag",) _hash_prefix = u("_") checksum_chars = u("ab") checksum_size = 1 def __init__(self, flag=False, **kwds): super(d1, self).__init__(**kwds) self.flag = flag def _calc_checksum(self, secret): return u('b') if self.flag else u('a') # check default identify method self.assertTrue(d1.identify(u('_a'))) self.assertTrue(d1.identify(b('_a'))) self.assertTrue(d1.identify(u('_b'))) self.assertFalse(d1.identify(u('_c'))) self.assertFalse(d1.identify(b('_c'))) self.assertFalse(d1.identify(u('a'))) self.assertFalse(d1.identify(u('b'))) self.assertFalse(d1.identify(u('c'))) self.assertRaises(TypeError, d1.identify, None) self.assertRaises(TypeError, d1.identify, 1) # check default genconfig method self.assertIs(d1.genconfig(), None) # check default verify method self.assertTrue(d1.verify('s', b('_a'))) self.assertTrue(d1.verify('s',u('_a'))) self.assertFalse(d1.verify('s', b('_b'))) self.assertFalse(d1.verify('s',u('_b'))) self.assertTrue(d1.verify('s', b('_b'), flag=True)) self.assertRaises(ValueError, d1.verify, 's', b('_c')) self.assertRaises(ValueError, d1.verify, 's', u('_c')) # check default encrypt method self.assertEqual(d1.encrypt('s'), '_a') self.assertEqual(d1.encrypt('s', flag=True), '_b')
import logging; log = logging.getLogger(__name__) from warnings import warn # site # pkg from lib.passlib.utils import classproperty, h64, safe_crypt, test_crypt from lib.passlib.utils.compat import b, bytes, u, uascii_to_str, unicode from lib.passlib.utils.pbkdf2 import get_prf import lib.passlib.utils.handlers as uh # local __all__ = [ ] #============================================================================= # sha1-crypt #============================================================================= _hmac_sha1 = get_prf("hmac-sha1")[0] _BNULL = b('\x00') class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler): """This class implements the SHA1-Crypt password hash, and follows the :ref:`password-hash-api`. It supports a variable-length salt, and a variable number of rounds. The :meth:`~passlib.ifc.PasswordHash.encrypt` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept the following optional keywords: :type salt: str :param salt: Optional salt string. If not specified, an 8 character one will be autogenerated (this is recommended). If specified, it must be 0-64 characters, drawn from the regexp range ``[./0-9A-Za-z]``. :type salt_size: int
return self.checksum else: return u("!") @classmethod def verify(cls, secret, hash, enable_wildcard=False): uh.validate_secret(secret) if not isinstance(hash, base_string_types): raise uh.exc.ExpectedStringError(hash, "hash") elif hash: return False else: return enable_wildcard _MARKER_CHARS = u("*!") _MARKER_BYTES = b("*!") class unix_disabled(uh.PasswordHash): """This class provides disabled password behavior for unix shadow files, and follows the :ref:`password-hash-api`. This class does not implement a hash, but instead matches the "disabled account" strings found in ``/etc/shadow`` on most Unix variants. "encrypting" a password will simply return the disabled account marker. It will reject all passwords, no matter the hash string. The :meth:`~passlib.ifc.PasswordHash.encrypt` method supports one optional keyword: :type marker: str :param marker: Optional marker string which overrides the platform default used to indicate a disabled account.
from warnings import warn # site # pkg from lib.passlib.utils import classproperty, h64, safe_crypt, test_crypt, repeat_string from lib.passlib.utils.compat import b, bytes, irange, unicode, u import lib.passlib.utils.handlers as uh # local __all__ = [ "md5_crypt", "apr_md5_crypt", ] #============================================================================= # pure-python backend #============================================================================= _BNULL = b("\x00") _MD5_MAGIC = b("$1$") _APR_MAGIC = b("$apr1$") # pre-calculated offsets used to speed up C digest stage (see notes below). # sequence generated using the following: ##perms_order = "p,pp,ps,psp,sp,spp".split(",") ##def offset(i): ## key = (("p" if i % 2 else "") + ("s" if i % 3 else "") + ## ("p" if i % 7 else "") + ("" if i % 2 else "p")) ## return perms_order.index(key) ##_c_digest_offsets = [(offset(i), offset(i+1)) for i in range(0,42,2)] _c_digest_offsets = ( (0, 3), (5, 1), (5, 3), (1, 2), (5, 1), (5, 3), (1, 3), (4, 1), (5, 3), (1, 3), (5, 0), (5, 3), (1, 3), (5, 1), (4, 3), (1, 3), (5, 1), (5, 2), (1, 3), (5, 1), (5, 3),
from lib.passlib.hash import htdigest from lib.passlib.utils import consteq, render_bytes, to_bytes, deprecated_method, is_ascii_codec from lib.passlib.utils.compat import b, bytes, join_bytes, str_to_bascii, u, \ unicode, BytesIO, iteritems, imap, PY3 # local __all__ = [ 'HtpasswdFile', 'HtdigestFile', ] #============================================================================= # constants & support #============================================================================= _UNSET = object() _BCOLON = b(":") # byte values that aren't allowed in fields. _INVALID_FIELD_CHARS = b(":\n\r\t\x00") #============================================================================= # backport of OrderedDict for PY2.5 #============================================================================= try: from collections import OrderedDict except ImportError: # Python 2.5 class OrderedDict(dict): """hacked OrderedDict replacement. NOTE: this doesn't provide a full OrderedDict implementation,
# derived handlers #------------------------------------------------------------------------ pbkdf2_sha1 = create_pbkdf2_hash("sha1", 20, 131000, ident=u("$pbkdf2$")) pbkdf2_sha256 = create_pbkdf2_hash("sha256", 32, 29000) pbkdf2_sha512 = create_pbkdf2_hash("sha512", 64, 25000) ldap_pbkdf2_sha1 = uh.PrefixWrapper("ldap_pbkdf2_sha1", pbkdf2_sha1, "{PBKDF2}", "$pbkdf2$", ident=True) ldap_pbkdf2_sha256 = uh.PrefixWrapper("ldap_pbkdf2_sha256", pbkdf2_sha256, "{PBKDF2-SHA256}", "$pbkdf2-sha256$", ident=True) ldap_pbkdf2_sha512 = uh.PrefixWrapper("ldap_pbkdf2_sha512", pbkdf2_sha512, "{PBKDF2-SHA512}", "$pbkdf2-sha512$", ident=True) #============================================================================= # cryptacular's pbkdf2 hash #============================================================================= # bytes used by cta hash for base64 values 63 & 64 CTA_ALTCHARS = b("-_") class cta_pbkdf2_sha1(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): """This class implements Cryptacular's PBKDF2-based crypt algorithm, and follows the :ref:`password-hash-api`. It supports a variable-length salt, and a variable number of rounds. 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 bytes. If specified, it may be any length. If not specified, a one will be autogenerated (this is recommended). :type salt_size: int
MAGIC_HAMLET = b( "To be, or not to be,--that is the question:--\n" "Whether 'tis nobler in the mind to suffer\n" "The slings and arrows of outrageous fortune\n" "Or to take arms against a sea of troubles,\n" "And by opposing end them?--To die,--to sleep,--\n" "No more; and by a sleep to say we end\n" "The heartache, and the thousand natural shocks\n" "That flesh is heir to,--'tis a consummation\n" "Devoutly to be wish'd. To die,--to sleep;--\n" "To sleep! perchance to dream:--ay, there's the rub;\n" "For in that sleep of death what dreams may come,\n" "When we have shuffled off this mortal coil,\n" "Must give us pause: there's the respect\n" "That makes calamity of so long life;\n" "For who would bear the whips and scorns of time,\n" "The oppressor's wrong, the proud man's contumely,\n" "The pangs of despis'd love, the law's delay,\n" "The insolence of office, and the spurns\n" "That patient merit of the unworthy takes,\n" "When he himself might his quietus make\n" "With a bare bodkin? who would these fardels bear,\n" "To grunt and sweat under a weary life,\n" "But that the dread of something after death,--\n" "The undiscover'd country, from whose bourn\n" "No traveller returns,--puzzles the will,\n" "And makes us rather bear those ills we have\n" "Than fly to others that we know not of?\n" "Thus conscience does make cowards of us all;\n" "And thus the native hue of resolution\n" "Is sicklied o'er with the pale cast of thought;\n" "And enterprises of great pith and moment,\n" "With this regard, their currents turn awry,\n" "And lose the name of action.--Soft you now!\n" "The fair Ophelia!--Nymph, in thy orisons\n" "Be all my sins remember'd.\n\x00" #<- apparently null at end of C string is included (test vector won't pass otherwise) )
def _calc_checksum(self, secret): if isinstance(secret, unicode): secret = secret.encode("utf-8") data = b("boblious") + secret return str_to_uascii(hashlib.sha1(data).hexdigest())
def _stub_checksum(self): return b('\x00') * self.checksum_size
from binascii import hexlify # site # pkg from lib.passlib.utils.compat import b, unicode from lib.passlib.utils.des import des_encrypt_block from lib.passlib.hash import nthash # local __all__ = [ "nthash", "raw_lmhash", "raw_nthash", ] #============================================================================= # helpers #============================================================================= LM_MAGIC = b("KGS!@#$%") raw_nthash = nthash.raw_nthash 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 its 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
import lib.passlib.utils.handlers as uh # local __all__ = [ "mssql2000", "mssql2005", ] #============================================================================= # mssql 2000 #============================================================================= def _raw_mssql(secret, salt): assert isinstance(secret, unicode) assert isinstance(salt, bytes) return sha1(secret.encode("utf-16-le") + salt).digest() BIDENT = b("0x0100") ##BIDENT2 = b("\x01\x00") UIDENT = u("0x0100") def _ident_mssql(hash, csize, bsize): """common identify for mssql 2000/2005""" if isinstance(hash, unicode): if len(hash) == csize and hash.startswith(UIDENT): return True elif isinstance(hash, bytes): if len(hash) == csize and hash.startswith(BIDENT): return True ##elif len(hash) == bsize and hash.startswith(BIDENT2): # raw bytes ## return True else: raise uh.exc.ExpectedStringError(hash, "hash")
def md4(content=None): """wrapper for hashlib.new('md4')""" return hashlib.new('md4', content or b(''))
def __init__(self, content=None): self._count = 0 self._state = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476] self._buf = b('') if content: self.update(content)
result = check_table(name) if result: return result # else we've done what we can warn("norm_hash_name(): unknown hash: %r" % (orig,), PasslibRuntimeWarning) name2 = name.replace("-", "") row = _nhn_cache[orig] = (name2, name) return row[idx] # TODO: get_hash() func which wraps norm_hash_name(), hashlib.<attr>, and hashlib.new #============================================================================= # general prf lookup #============================================================================= _BNULL = b('\x00') _XY_DIGEST = b(',\x1cb\xe0H\xa5\x82M\xfb>\xd6\x98\xef\x8e\xf9oQ\x85\xa3i') _trans_5C = join_byte_values((x ^ 0x5C) for x in irange(256)) _trans_36 = join_byte_values((x ^ 0x36) for x in irange(256)) def _get_hmac_prf(digest): """helper to return HMAC prf for specific digest""" 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 its default digest