summaryrefslogtreecommitdiff
path: root/passlib/utils
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2011-02-14 02:04:44 -0500
committerEli Collins <elic@assurancetechnologies.com>2011-02-14 02:04:44 -0500
commit43dfc4084ada0d073556b0aa26d67952f8a2a4e0 (patch)
treec0f1ad2243d239d6a25d287354a976b692898ba0 /passlib/utils
parent646a0449d96327e0643a3181878e39125c872d45 (diff)
downloadpasslib-43dfc4084ada0d073556b0aa26d67952f8a2a4e0.tar.gz
convert BCrypt, MySQL 41 to classes
Diffstat (limited to 'passlib/utils')
-rw-r--r--passlib/utils/_slow_bcrypt.py130
-rw-r--r--passlib/utils/handlers.py40
2 files changed, 63 insertions, 107 deletions
diff --git a/passlib/utils/_slow_bcrypt.py b/passlib/utils/_slow_bcrypt.py
index 820f465..3b4d204 100644
--- a/passlib/utils/_slow_bcrypt.py
+++ b/passlib/utils/_slow_bcrypt.py
@@ -67,8 +67,7 @@ from struct import pack
from passlib.utils import rng, getrandbytes
#local
__all__ = [
- 'hashpw',
- 'gensalt',
+ 'raw_bcrypt',
]
#=========================================================
#bcrypt/blowfish constants
@@ -525,14 +524,13 @@ def encode_hash(minor, rounds, salt, chk=None):
"helper for formatting hash string"
if not minor or minor == '\x00':
minor = ''
- salt = encode_base64(salt)
- if chk:
- return '$2%s$%02d$%s%s' % (minor, rounds, salt, encode_base64(chk))
- else:
- return '$2%s$%02d$%s' % (minor, rounds, salt)
+ return '$2%s$%02d$%s%s' % (minor, rounds, salt, chk or '')
#=========================================================
-#hash class
+#base bcrypt helper
+#
+#interface designed only for use by passlib.hash.bcrypt.BCrypt
+#probably not suitable for other purposes
#=========================================================
RPLEN = range(0, BLOWFISH_P_LEN)
@@ -549,23 +547,42 @@ MASK_32 = 0xFfffFfff
CHK_PACK = ">" + "I" * BF_CRYPT_CIPHERLEN
-def raw_bcrypt(password, salt, log_rounds):
+def raw_bcrypt(password, ident, salt, log_rounds):
"""perform central password hashing step in bcrypt scheme.
:param password: the password to hash
- :param salt: the binary salt to use
- :param rounds: the log2 of the number of rounds
- :returns: array containing hashed password
+ :param ident: identifier w/ minor version (eg 2, 2a)
+ :param salt: the binary salt to use (encoded in bcrypt-base64)
+ :param rounds: the log2 of the number of rounds (as int)
+ :returns: bcrypt-base64 encoded checksum
"""
- # password byte[]
- # salt byte[]
- # log_rounds int
- # returns byte[]
+ #
+ #parse inputs
+ #
+
+ #parse ident
+ if ident == '2':
+ minor = 0
+ elif ident == '2a':
+ minor = 1
+ else:
+ raise ValueError, "unknown ident: %r" % (ident,)
+
+ #decode & validate salt
+ salt = decode_base64(salt)
+ if len(salt) < BCRYPT_SALT_LEN:
+ raise ValueError, "Missing salt bytes"
+ elif len(salt) > BCRYPT_SALT_LEN:
+ salt = salt[:BCRYPT_SALT_LEN]
+ #prepare password
+ password = password.encode("utf-8")
+ if minor > 0:
+ password += '\x00'
+
+ #validate rounds
if log_rounds < BCRYPT_MIN_ROUNDS or log_rounds > BCRYPT_MAX_ROUNDS:
raise ValueError, "Bad number of rounds"
- if len(salt) != BCRYPT_SALT_LEN:
- raise ValueError, "Bad salt length: %r" % salt
#
#init blowfish key schedule
@@ -647,6 +664,8 @@ def raw_bcrypt(password, salt, log_rounds):
P[i], P[i+1] = l,r = encipher(l,r)
for i in RSLEN_S2:
S[i], S[i+1] = l,r = encipher(l,r)
+ if rounds % 10 == 0:
+ print rounds
#
#encipher constant data
@@ -663,79 +682,8 @@ def raw_bcrypt(password, salt, log_rounds):
#
#encode cdata buffer to bytes
#
- return pack(CHK_PACK, *cdata)[:-1]
-
-#=========================================================
-#frontends
-#=========================================================
-
-def hashpw(password, salt):
- """Hash a password using the OpenBSD bcrypt scheme.
-
- :param password:
- the password to hash
- :param salt:
- the salt to hash with (generated via gensalt)
- :returns:
- the hashed password
- """
- # "$2$rd$saltchk"
- # "$2a$rd$saltchk"
-
- #extract version number
- if not salt.startswith("$2"):
- raise ValueError, "Invalid salt version"
- if salt[2] == '$':
- minor = '\x00'
- off = 3
- else:
- minor = salt[2]
- if minor != 'a' or salt[3] != '$':
- raise ValueError, "Invalid salt revision"
- off = 4
-
- #extract number of rounds
- if salt[off+2] != '$':
- raise ValueError, "Missing salt rounds"
- rounds = int(salt[off:off+2])
-
- #extract salt string
- real_salt = salt[off+3:off+25] #25-3=22, 22*3/4=16.5, 16.5=.5 + BCRYPT_SALT_LEN
- saltb = decode_base64(real_salt)
- if len(saltb) < BCRYPT_SALT_LEN:
- raise ValueError, "Missing salt bytes"
- elif len(saltb) > BCRYPT_SALT_LEN:
- saltb = saltb[:BCRYPT_SALT_LEN]
-
- #prepare password
- passwordb = password.encode("utf-8")
- if minor >= 'a':
- passwordb += '\x00'
-
- #encrypt password
- chk = raw_bcrypt(passwordb, saltb, rounds)
-
- #return hash string
- if minor < 'a':
- minor = ''
- return encode_hash(minor, rounds, saltb, chk)
-
-def gensalt(log_rounds=BCRYPT_DEFAULT_ROUNDS):
- """Generate a salt for use with the BCrypt.hashpw() method.
-
- :param log_rounds:
- the log2 of the number of rounds of
- hashing to apply - the work factor therefore increases as
- 2**log_rounds.
- :returns:
- the encoded random salt value
- """
- salt = getrandbytes(rng, BCRYPT_SALT_LEN)
- if log_rounds < BCRYPT_MIN_ROUNDS:
- log_rounds = BCRYPT_MIN_ROUNDS
- elif log_rounds > BCRYPT_MAX_ROUNDS:
- log_rounds = BCRYPT_MAX_ROUNDS
- return encode_hash('a', log_rounds, salt)
+ raw = pack(CHK_PACK, *cdata)[:-1]
+ return encode_base64(raw)
#=========================================================
#eof
diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py
index 903b070..74c2f7b 100644
--- a/passlib/utils/handlers.py
+++ b/passlib/utils/handlers.py
@@ -435,6 +435,7 @@ class BaseHandler(object):
#=========================================================
#plain - mysql_323, mysql_41, nthash, postgres_md5
#=========================================================
+#XXX: rename this? StaticHandler? NoSettingHandler? and give this name to WrapperHandler
class PlainHandler(object):
"""helper class optimized for implementing hash schemes which have NO settings whatsoever"""
#=========================================================
@@ -537,24 +538,13 @@ class WrapperHandler(object):
context_kwds = ()
#=====================================================
- #required methods
- #=====================================================
- @classmethod
- def genconfig(cls, **settings):
- raise NotImplementedError, "%s subclass must implement genconfig()" % (cls,)
-
- @classmethod
- def genhash(cls, secret, config):
- raise NotImplementedError, "%s subclass must implement genhash()" % (cls,)
-
- #=====================================================
- #default methods (usually subclassed)
+ #formatting (usually subclassed)
#=====================================================
@classmethod
def identify(cls, hash):
- #NOTE: relying on genhash throwing error for invalid,
- # but takes a long time for non-invalid.
- # subclasses *really* should override this.
+ #NOTE: this relys on genhash throwing error for invalid hashes.
+ # this approach is bad because genhash may take a long time on valid hashes,
+ # so subclasses *really* should override this.
try:
cls.genhash('stub', hash)
return True
@@ -562,7 +552,23 @@ class WrapperHandler(object):
return False
#=====================================================
- #default methods (rarely subclassed)
+ #primary interface (must be subclassed)
+ #=====================================================
+ @classmethod
+ def genconfig(cls, **settings):
+ if cls.setting_kwds:
+ raise NotImplementedError, "%s subclass must implement genconfig()" % (cls,)
+ else:
+ if settings:
+ raise TypeError, "%s genconfig takes no kwds" % (cls.name,)
+ return None
+
+ @classmethod
+ def genhash(cls, secret, config):
+ raise NotImplementedError, "%s subclass must implement genhash()" % (cls,)
+
+ #=====================================================
+ #secondary interface (rarely subclassed)
#=====================================================
@classmethod
def encrypt(cls, secret, **settings):
@@ -571,6 +577,8 @@ class WrapperHandler(object):
@classmethod
def verify(cls, secret, hash):
+ if not hash:
+ raise ValueError, "no hash specified"
return hash == cls.genhash(secret, hash)
#=====================================================