diff options
| author | Eli Collins <elic@assurancetechnologies.com> | 2011-02-01 21:28:10 +0000 |
|---|---|---|
| committer | Eli Collins <elic@assurancetechnologies.com> | 2011-02-01 21:28:10 +0000 |
| commit | 6caf4d7cdedccb042d66671de7b56d05718ed322 (patch) | |
| tree | 7813968d86879249968fbea00ef9c42be6da1b24 /passlib | |
| parent | aa1b4e5f72fd6c73c4a1bce3de01d23e98f0e257 (diff) | |
| download | passlib-6caf4d7cdedccb042d66671de7b56d05718ed322.tar.gz | |
added nthash support; document updates
Diffstat (limited to 'passlib')
| -rw-r--r-- | passlib/hash/nthash.py | 99 | ||||
| -rw-r--r-- | passlib/hash/phpass.py | 2 | ||||
| -rw-r--r-- | passlib/tests/handler_utils.py | 6 | ||||
| -rw-r--r-- | passlib/tests/test_hash_misc.py (renamed from passlib/tests/test_hash_phpass.py) | 25 | ||||
| -rw-r--r-- | passlib/utils/h64.py | 45 | ||||
| -rw-r--r-- | passlib/win32.py | 12 |
6 files changed, 162 insertions, 27 deletions
diff --git a/passlib/hash/nthash.py b/passlib/hash/nthash.py new file mode 100644 index 0000000..543122a --- /dev/null +++ b/passlib/hash/nthash.py @@ -0,0 +1,99 @@ +"""passlib.hash.nthash - unix-crypt compatible nthash passwords""" +#========================================================= +#imports +#========================================================= +#core +import re +import logging; log = logging.getLogger(__name__) +from warnings import warn +#site +#libs +from passlib.utils.md4 import md4 +#pkg +#local +__all__ = [ + "genhash", + "genconfig", + "encrypt", + "identify", + "verify", +] + +#========================================================= +#backend +#========================================================= +def raw_nthash(secret, hex=False): + "encode password using md4-based NTHASH algorithm; returns string of raw bytes" + hash = md4(secret.encode("utf-16le")) + return hash.hexdigest() if hex else hash.digest() + +#========================================================= +#algorithm information +#========================================================= +name = "nthash" +#stats: 128 bit checksum, no salt + +setting_kwds = () +context_kwds = () + +#========================================================= +#internal helpers +#========================================================= +_pat = re.compile(r""" + ^ + \$(?P<ident>3\$\$|NT\$) + (?P<chk>[a-f0-9]{32}) + $ + """, re.X) + +def parse(hash): + if not hash: + raise ValueError, "no hash specified" + m = _pat.match(hash) + if not m: + raise ValueError, "invalid nthash" + ident, chk = m.group("ident", "chk") + out = dict( + checksum=chk, + ) + ident=ident.strip("$") + if ident != "3": + out['ident'] = ident + return out + +def render(checksum, ident=None): + if not ident or ident == "3": + return "$3$$" + checksum + elif ident == "NT": + return "$NT$" + checksum + else: + raise ValueError, "invalid ident" + +#========================================================= +#primary interface +#========================================================= +def genconfig(ident=None): + return render("0" * 32, ident) + +def genhash(secret, config): + info = parse(config) + if secret is None: + raise TypeError, "secret must be a string" + chk = raw_nthash(secret, hex=True) + return render(chk, info.get('ident')) + +#========================================================= +#secondary interface +#========================================================= +def encrypt(secret, **settings): + return genhash(secret, genconfig(**settings)) + +def verify(secret, hash): + return hash == genhash(secret, hash) + +def identify(hash): + return bool(hash and _pat.match(hash)) + +#========================================================= +#eof +#========================================================= diff --git a/passlib/hash/phpass.py b/passlib/hash/phpass.py index 5be4708..b161073 100644 --- a/passlib/hash/phpass.py +++ b/passlib/hash/phpass.py @@ -100,7 +100,7 @@ def genconfig(salt=None, rounds=None, ident="P"): :param ident: phpBB3 uses ``H`` instead of ``P`` for it's identifier. - this may be set to generate phpBB3 compatible hashes. + this may be set to ``H`` in order to generate phpBB3 compatible hashes. :returns: phpass configuration string. diff --git a/passlib/tests/handler_utils.py b/passlib/tests/handler_utils.py index e88e73c..f6e1981 100644 --- a/passlib/tests/handler_utils.py +++ b/passlib/tests/handler_utils.py @@ -241,7 +241,7 @@ class _HandlerTestCase(TestCase): "test secret_chars limit" sc = self.secret_chars - base = "too many secrets" + base = "too many secrets" #16 chars alt = 'x' #char that's not in base string if sc > 0: @@ -268,7 +268,9 @@ class _HandlerTestCase(TestCase): #NOTE: this doesn't do an exhaustive search to verify algorithm #doesn't have some cutoff point, it just tries - #1024-character string, and alters the last char + #1024-character string, and alters the last char. + #as long as algorithm doesn't clip secret at point <1024, + #the new secret shouldn't verify. secret = base * 64 hash = self.do_encrypt(secret) self.assert_(not self.do_verify(secret[:-1] + alt, hash)) diff --git a/passlib/tests/test_hash_phpass.py b/passlib/tests/test_hash_misc.py index af90bd8..da3d0ba 100644 --- a/passlib/tests/test_hash_phpass.py +++ b/passlib/tests/test_hash_misc.py @@ -10,15 +10,16 @@ from logging import getLogger #pkg from passlib.tests.handler_utils import _HandlerTestCase from passlib.tests.utils import enable_option -import passlib.hash.phpass as mod #module log = getLogger(__name__) #========================================================= -#md5 crypt +#PHPass Portable Crypt #========================================================= +from passlib.hash import phpass + class PHPassTest(_HandlerTestCase): - handler = mod + handler = phpass known_correct = ( ('', '$P$7JaFQsPzJSuenezefD/3jHgt5hVfNH0'), @@ -32,5 +33,23 @@ class PHPassTest(_HandlerTestCase): ) #========================================================= +#NTHASH for unix +#========================================================= +from passlib.hash import nthash + +class NTHashTest(_HandlerTestCase): + handler = nthash + + known_correct = ( + ('passphrase', '$3$$7f8fe03093cc84b267b109625f6bbf4b'), + ('passphrase', '$NT$7f8fe03093cc84b267b109625f6bbf4b'), + ) + + known_invalid = ( + #bad char in otherwise correct hash + '$3$$7f8fe03093cc84b267b109625f6bbfxb', + ) + +#========================================================= #EOF #========================================================= diff --git a/passlib/utils/h64.py b/passlib/utils/h64.py index 78ac13c..90e14c2 100644 --- a/passlib/utils/h64.py +++ b/passlib/utils/h64.py @@ -10,15 +10,16 @@ import logging; log = logging.getLogger(__name__) __all__ = [ "CHARS", - "decode_6bit", "encode_6bit", - + "encode_bytes", "encode_3_offsets", "encode_2_offsets", "encode_1_offset", + "decode_int6", "encode_int6", "decode_int12", "encode_int12" "decode_int24", "encode_int24", "decode_int64", "encode_int64", + "decode_int", ] #================================================================================= @@ -92,17 +93,15 @@ def encode_bytes(source): # int <-> b64 string, used by des_crypt, ext_des_crypt #================================================================================= -def decode_int(value): - "decode hash-64 format used by crypt into integer" - #FORMAT: little-endian, each char contributes 6 bits, - # char value = index in H64_CHARS string - try: - out = 0 - for c in reversed(value): - out = (out<<6) + b64_decode_6bit(c) - return out - except KeyError: - raise ValueError, "invalid character in string" +def encode_int6(value): + "encode 6 bit integer to single char of hash-64 format" + return encode_6bit(value) + +def decode_int6(value): + "decode 1 char of hash-64 format, returning 6-bit integer" + return decode_6bit(value) + +#--------------------------------------------------------------------- def decode_int12(value): "decode 2 chars of hash-64 format used by crypt, returning 12-bit integer" @@ -115,6 +114,8 @@ def encode_int12(value): "encode 2 chars of hash-64 format from a 12-bit integer" return encode_6bit(value & 0x3f) + encode_6bit((value>>6) & 0x3f) +#--------------------------------------------------------------------- + def decode_int24(value): "decode 4 chars of hash-64 format, returning 24-bit integer" try: @@ -132,6 +133,8 @@ def encode_int24(value): encode_6bit((value>>12) & 0x3f) + \ encode_6bit((value>>18) & 0x3f) +#--------------------------------------------------------------------- + _RR9_1 = range(9,-1,-1) def decode_int64(value): @@ -147,6 +150,22 @@ def encode_int64(value): value >>= 6 return "".join(out) +#--------------------------------------------------------------------- + +def decode_int(value): + "decode hash-64 format used by crypt into integer" + #FORMAT: little-endian, each char contributes 6 bits, + # char value = index in H64_CHARS string + try: + out = 0 + for c in reversed(value): + out = (out<<6) + b64_decode_6bit(c) + return out + except KeyError: + raise ValueError, "invalid character in string" + +## def encode_int(value): + #================================================================================= #eof #================================================================================= diff --git a/passlib/win32.py b/passlib/win32.py index 9c90b59..1b55141 100644 --- a/passlib/win32.py +++ b/passlib/win32.py @@ -26,11 +26,12 @@ from binascii import hexlify #site #pkg from passlib.utils.des import des_encrypt_block -from passlib.utils.md4 import md4 +from passlib.hash import nthash +from passlib.hash.nthash import raw_nthash #local __all__ = [ - "lmhash", - "nthash", + "raw_lmhash", + "raw_nthash", ] #========================================================= #helpers @@ -44,11 +45,6 @@ def raw_lmhash(secret, hex=False): out = des_encrypt_block(ns[:7], LM_MAGIC) + des_encrypt_block(ns[7:], LM_MAGIC) return hexlify(out) if hex else out -def raw_nthash(secret, hex=False): - "encode password using md4-based NTHASH algorithm; returns string of raw bytes" - hash = md4(secret.encode("utf-16le")) - return hash.hexdigest() if hex else hash.digest() - #========================================================= #eoc #========================================================= |
