diff options
| author | Eli Collins <elic@assurancetechnologies.com> | 2011-02-09 12:32:11 -0500 |
|---|---|---|
| committer | Eli Collins <elic@assurancetechnologies.com> | 2011-02-09 12:32:11 -0500 |
| commit | bdcd1f2997c606994f199f7db45bf52a2a41c1b0 (patch) | |
| tree | a2aaea7cc1d559d747566748af8bf67bde537de9 | |
| parent | 15f6fe8633ceb47695e05ff1bc0d7b95db0e3075 (diff) | |
| download | passlib-bdcd1f2997c606994f199f7db45bf52a2a41c1b0.tar.gz | |
added netbsd's sha1-crypt (needs doc)
| -rw-r--r-- | docs/notes.txt | 3 | ||||
| -rw-r--r-- | passlib/hash/sha1_crypt.py | 159 | ||||
| -rw-r--r-- | passlib/tests/test_hash_misc.py | 18 | ||||
| -rw-r--r-- | passlib/unix.py | 4 |
4 files changed, 182 insertions, 2 deletions
diff --git a/docs/notes.txt b/docs/notes.txt index 9137e4d..204a4ef 100644 --- a/docs/notes.txt +++ b/docs/notes.txt @@ -176,6 +176,9 @@ oracle - http://www.notesbit.com/index.php/scripts-oracle/oracle-11g-new-password-algorithm-is-revealed-by-seclistsorg/ http://www.users.zetnet.co.uk/hopwood/crypto/scan/ph.html + +lots of sample hashes + http://openwall.info/wiki/john/sample-hashes =========== scrpyt http://www.tarsnap.com/scrypt.html diff --git a/passlib/hash/sha1_crypt.py b/passlib/hash/sha1_crypt.py new file mode 100644 index 0000000..4811517 --- /dev/null +++ b/passlib/hash/sha1_crypt.py @@ -0,0 +1,159 @@ +"""passlib.sha1_crypt + +Implementation of NetBSD's sha1-based crypt algorithm. + +from source - +http://fxr.googlebit.com/source/lib/libcrypt/crypt-sha1.c?v=NETBSD-CURRENT + +thread discussing md5-crypt vs sha1-crypt +http://osdir.com/ml/os.netbsd.devel.security/2005-06/msg00025.html + +when sha1-crypt was added - +http://mail-index.netbsd.org/tech-userlevel/2004/05/29/0001.html +""" + +#$sha1$19703$iVdJqfSE$v4qYKl1zqYThwpjJAoKX6UvlHq/a +#$sha1$21773$uV7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH + +#========================================================= +#imports +#========================================================= +from __future__ import with_statement, absolute_import +#core +from hmac import new as hmac +from hashlib import sha1 +import re +import logging; log = logging.getLogger(__name__) +from warnings import warn +#site +try: + from M2Crypto import EVP as _EVP +except ImportError: + _EVP = None +#libs +from passlib.utils import norm_rounds, norm_salt, autodocument, h64 +#pkg +#local +__all__ = [ +] + +#========================================================= +#backend +#========================================================= +def hmac_sha1(key, msg): + return hmac(key, msg, sha1).digest() + +if _EVP: + try: + result = _EVP.hmac('x','y') #default *should* be sha1, which saves us a wrapper + except ValueError: + pass + else: + if result == ',\x1cb\xe0H\xa5\x82M\xfb>\xd6\x98\xef\x8e\xf9oQ\x85\xa3i': + hmac_sha1 = _EVP.hmac + +#TODO: should test for crypt support (NetBSD only) + +#========================================================= +#algorithm information +#========================================================= +name = "sha1_crypt" +#stats: ?? bit checksum, ?? bit salt, 2**(4..31) rounds + +setting_kwds = ("salt", "rounds") +context_kwds = () + +default_salt_chars = 8 +min_salt_chars = 0 +max_salt_chars = 64 + +default_rounds = 40000 #current passlib default + #todo: vary default value by some % down +min_rounds = 1 #really, this should be higher. +max_rounds = 4294967295 # 32-bit integer limit +rounds_cost = "linear" + +#========================================================= +#internal helpers +#========================================================= +_pat = re.compile(r""" + ^ + \$sha1 + \$(?P<rounds>\d+) + \$(?P<salt>[A-Za-z0-9./]{0,64}) + (\$(?P<chk>[A-Za-z0-9./]{28}))? + $ + """, re.X) + +def parse(hash): + if not hash: + raise ValueError, "no hash specified" + m = _pat.match(hash) + if not m: + raise ValueError, "invalid sha1_crypt hash" + rounds, salt, chk = m.group("rounds", "salt", "chk") + return dict( + rounds=int(rounds), + salt=salt, + checksum=chk, + ) + +def render(rounds, salt, checksum=None): + out = "$sha1$%d$%s" % (rounds, salt) + if checksum: + out += "$" + checksum + return out + +#========================================================= +#primary interface +#========================================================= +def genconfig(salt=None, rounds=None): + salt = norm_salt(salt, min_salt_chars, max_salt_chars, default_salt_chars, name=name) + rounds = norm_rounds(rounds, default_rounds, min_rounds, max_rounds, name=name) + return render(rounds, salt, None) + +def genhash(secret, config): + #parse and run through genconfig to validate configuration + info = parse(config) + info.pop("checksum") + config = genconfig(**info) + info = parse(config) + rounds, salt = info['rounds'], info['salt'] + + if isinstance(secret, unicode): + secret = secret.encode("utf-8") + + result = salt + "$sha1$" + str(rounds) + r = 0 + while r < rounds: + result = hmac_sha1(secret, result) + r += 1 + chk = h64.encode_transposed_bytes(result, _chk_offsets) + return render(rounds, salt, chk) + +_chk_offsets = [ + 2,1,0, + 5,4,3, + 8,7,6, + 11,10,9, + 14,13,12, + 17,16,15, + 0,19,18, +] + +#========================================================= +#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)) + +autodocument(globals()) +#========================================================= +#eof +#========================================================= diff --git a/passlib/tests/test_hash_misc.py b/passlib/tests/test_hash_misc.py index da3d0ba..f99c4e4 100644 --- a/passlib/tests/test_hash_misc.py +++ b/passlib/tests/test_hash_misc.py @@ -51,5 +51,23 @@ class NTHashTest(_HandlerTestCase): ) #========================================================= +# netbsd sha1 crypt +#========================================================= +from passlib.hash import sha1_crypt + +class SHA1CryptTest(_HandlerTestcase): + handler = sha1_crypt + + known_correct = ( + ("password", "$sha1$19703$iVdJqfSE$v4qYKl1zqYThwpjJAoKX6UvlHq/a"), + ("password", "$sha1$21773$uV7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH"), + ) + + known_invalid = ( + #bad char in otherwise correct hash + '$sha1$21773$u!7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH', + ) + +#========================================================= #EOF #========================================================= diff --git a/passlib/unix.py b/passlib/unix.py index 8e8beca..32616db 100644 --- a/passlib/unix.py +++ b/passlib/unix.py @@ -74,12 +74,12 @@ linux_context = CryptContext([ "unix_disabled", "des_crypt", "md5_crypt", "sha25 #referencing source via -http://fxr.googlebit.com # freebsd 6,7,8 - des, md5, bcrypt, nthash -# netbsd - des, ext, md5, bcrypt, sha1 (TODO) +# netbsd - des, ext, md5, bcrypt, sha1 # openbsd - des, ext, md5, bcrypt bsd_context = CryptContext(["unix_disabled", "nthash", "des_crypt", "ext_des_crypt", "md5_crypt", "bcrypt"]) freebsd_context = CryptContext([ "unix_disabled", "des_crypt", "nthash", "md5_crypt", "bcrypt"]) openbsd_context = CryptContext([ "unix_disabled", "des_crypt", "ext_des_crypt", "md5_crypt", "bcrypt"]) -netbsd_context = CryptContext([ "unix_disabled", "des_crypt", "ext_des_crypt", "md5_crypt", "bcrypt"]) +netbsd_context = CryptContext([ "unix_disabled", "des_crypt", "ext_des_crypt", "md5_crypt", "bcrypt", "sha1_crypt"]) #========================================================= #eof |
