summaryrefslogtreecommitdiff
path: root/passlib
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2011-02-01 21:28:10 +0000
committerEli Collins <elic@assurancetechnologies.com>2011-02-01 21:28:10 +0000
commit6caf4d7cdedccb042d66671de7b56d05718ed322 (patch)
tree7813968d86879249968fbea00ef9c42be6da1b24 /passlib
parentaa1b4e5f72fd6c73c4a1bce3de01d23e98f0e257 (diff)
downloadpasslib-6caf4d7cdedccb042d66671de7b56d05718ed322.tar.gz
added nthash support; document updates
Diffstat (limited to 'passlib')
-rw-r--r--passlib/hash/nthash.py99
-rw-r--r--passlib/hash/phpass.py2
-rw-r--r--passlib/tests/handler_utils.py6
-rw-r--r--passlib/tests/test_hash_misc.py (renamed from passlib/tests/test_hash_phpass.py)25
-rw-r--r--passlib/utils/h64.py45
-rw-r--r--passlib/win32.py12
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
#=========================================================