summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2012-04-30 21:19:06 -0400
committerEli Collins <elic@assurancetechnologies.com>2012-04-30 21:19:06 -0400
commitcf13872f382961729c922b6852f522364b167099 (patch)
tree590332de7fbc2779efce2fdbde67aaffc4d645f9
parentd3650f5a8458d84c4ef7886aced7e99ef3935bfd (diff)
downloadpasslib-cf13872f382961729c922b6852f522364b167099.tar.gz
all os_crypt hashes now forbidden NULL chars
-rw-r--r--passlib/handlers/bcrypt.py12
-rw-r--r--passlib/handlers/des_crypt.py27
-rw-r--r--passlib/handlers/md5_crypt.py14
-rw-r--r--passlib/handlers/sha1_crypt.py9
-rw-r--r--passlib/handlers/sha2_crypt.py8
5 files changed, 47 insertions, 23 deletions
diff --git a/passlib/handlers/bcrypt.py b/passlib/handlers/bcrypt.py
index 94fd9fd..77a99da 100644
--- a/passlib/handlers/bcrypt.py
+++ b/passlib/handlers/bcrypt.py
@@ -29,7 +29,7 @@ except ImportError: #pragma: no cover
from passlib.exc import PasslibHashWarning, PasslibSecurityWarning
from passlib.utils import bcrypt64, safe_crypt, repeat_string, \
classproperty, rng, getrandstr, test_crypt
-from passlib.utils.compat import bytes, u, uascii_to_str, unicode, str_to_uascii
+from passlib.utils.compat import bytes, b, u, uascii_to_str, unicode, str_to_uascii
import passlib.utils.handlers as uh
#pkg
@@ -52,6 +52,7 @@ IDENT_2 = u("$2$")
IDENT_2A = u("$2a$")
IDENT_2X = u("$2x$")
IDENT_2Y = u("$2y$")
+_BNULL = b('\x00')
#=========================================================
# handler
@@ -266,6 +267,8 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
# py3: not supported (patch submitted)
if isinstance(secret, unicode):
secret = secret.encode("utf-8")
+ if _BNULL in secret:
+ raise uh.exc.NullPasswordError(self)
config = self._get_config()
hash = pybcrypt_hashpw(secret, config)
assert hash.startswith(config) and len(hash) == len(config)+31
@@ -278,6 +281,11 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
# py3: not supported
if isinstance(secret, unicode):
secret = secret.encode("utf-8")
+ if _BNULL in secret:
+ # NOTE: especially important to forbid NULLs for bcryptor,
+ # since it happily accepts them, and then silently truncates
+ # the password at first one it encounters :(
+ raise uh.exc.NullPasswordError(self)
if self.ident == IDENT_2:
# bcryptor doesn't support $2$ hashes; but we can fake $2$ behavior
# using the $2a$ algorithm, by repeating the password until
@@ -299,6 +307,8 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
"Passlib to use instead.", PasslibSecurityWarning)
if isinstance(secret, unicode):
secret = secret.encode("utf-8")
+ if _BNULL in secret:
+ raise uh.exc.NullPasswordError(self)
chk = _builtin_bcrypt(secret, self.ident.strip("$"),
self.salt.encode("ascii"), self.rounds)
return chk.decode("ascii")
diff --git a/passlib/handlers/des_crypt.py b/passlib/handlers/des_crypt.py
index 2d206b8..81c689a 100644
--- a/passlib/handlers/des_crypt.py
+++ b/passlib/handlers/des_crypt.py
@@ -24,6 +24,8 @@ __all__ = [
#=========================================================
# pure-python backend for des_crypt family
#=========================================================
+_BNULL = b('\x00')
+
def _crypt_secret_to_key(secret):
"""convert secret to 64-bit DES key.
@@ -53,9 +55,14 @@ def _raw_des_crypt(secret, salt):
except ValueError: #pragma: no cover - always caught by class
raise ValueError("invalid chars in salt")
+ # gotta do something - no official policy since this predates unicode
+ if isinstance(secret, unicode):
+ secret = secret.encode("utf-8")
+ assert isinstance(secret, bytes)
+
# forbidding NULL char because underlying crypt() rejects them too.
- if b('\x00') in secret:
- raise ValueError("null char in secret")
+ if _BNULL in secret:
+ raise uh.exc.NullPasswordError(des_crypt)
# convert first 8 bytes of secret string into an integer
key_value = _crypt_secret_to_key(secret)
@@ -87,9 +94,14 @@ def _raw_bsdi_crypt(secret, rounds, salt):
except ValueError: #pragma: no cover - always caught by class
raise ValueError("invalid salt")
+ # gotta do something - no official policy since this predates unicode
+ if isinstance(secret, unicode):
+ secret = secret.encode("utf-8")
+ assert isinstance(secret, bytes)
+
# forbidding NULL char because underlying crypt() rejects them too.
- if b('\x00') in secret:
- raise ValueError("secret must be string without null bytes")
+ if _BNULL in secret:
+ raise uh.exc.NullPasswordError(bsdi_crypt)
# convert secret string into an integer
key_value = _bsdi_secret_to_key(secret)
@@ -162,9 +174,6 @@ class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
return test_crypt("test", 'abgOeLfPimXQo')
def _calc_checksum_builtin(self, secret):
- # gotta do something - no official policy since des-crypt predates unicode
- if isinstance(secret, unicode):
- secret = secret.encode("utf-8")
return _raw_des_crypt(secret, self.salt.encode("ascii")).decode("ascii")
def _calc_checksum_os_crypt(self, secret):
@@ -295,8 +304,6 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
return test_crypt("test", '_/...lLDAxARksGCHin.')
def _calc_checksum_builtin(self, secret):
- if isinstance(secret, unicode):
- secret = secret.encode("utf-8")
return _raw_bsdi_crypt(secret, self.rounds, self.salt.encode("ascii")).decode("ascii")
def _calc_checksum_os_crypt(self, secret):
@@ -363,7 +370,7 @@ class bigcrypt(uh.HasSalt, uh.GenericHandler):
def _norm_checksum(self, value):
value = super(bigcrypt, self)._norm_checksum(value)
if value and len(value) % 11:
- raise uh.exc.InvalidHashError(cls)
+ raise uh.exc.InvalidHashError(self)
return value
#=========================================================
diff --git a/passlib/handlers/md5_crypt.py b/passlib/handlers/md5_crypt.py
index 5810104..cd413d6 100644
--- a/passlib/handlers/md5_crypt.py
+++ b/passlib/handlers/md5_crypt.py
@@ -22,9 +22,9 @@ __all__ = [
#=========================================================
#pure-python backend
#=========================================================
-B_NULL = b("\x00")
-B_MD5_MAGIC = b("$1$")
-B_APR_MAGIC = b("$apr1$")
+_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:
@@ -72,6 +72,8 @@ def _raw_md5_crypt(pwd, salt, use_apr=False):
if isinstance(pwd, unicode):
pwd = pwd.encode("utf-8")
assert isinstance(pwd, bytes), "pwd not unicode or bytes"
+ if _BNULL in pwd:
+ raise uh.exc.NullPasswordError(md5_crypt)
pwd_len = len(pwd)
#validate salt - should have been taken care of by caller
@@ -84,9 +86,9 @@ def _raw_md5_crypt(pwd, salt, use_apr=False):
# load APR specific constants
if use_apr:
- magic = B_APR_MAGIC
+ magic = _APR_MAGIC
else:
- magic = B_MD5_MAGIC
+ magic = _MD5_MAGIC
#=====================================================================
# digest B - used as subinput to digest A
@@ -111,7 +113,7 @@ def _raw_md5_crypt(pwd, salt, use_apr=False):
i = pwd_len
evenchar = pwd[:1]
while i:
- a_ctx_update(B_NULL if i & 1 else evenchar)
+ a_ctx_update(_BNULL if i & 1 else evenchar)
i >>= 1
# finish A
diff --git a/passlib/handlers/sha1_crypt.py b/passlib/handlers/sha1_crypt.py
index 852b493..36d6780 100644
--- a/passlib/handlers/sha1_crypt.py
+++ b/passlib/handlers/sha1_crypt.py
@@ -25,6 +25,7 @@ __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`.
@@ -100,10 +101,12 @@ class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
def _calc_checksum_builtin(self, secret):
if isinstance(secret, unicode):
secret = secret.encode("utf-8")
+ if _BNULL in secret:
+ raise uh.exc.NullPasswordError(self)
rounds = self.rounds
- #NOTE: this uses a different format than the hash...
- result = u("%s$sha1$%s") % (self.salt, rounds)
- result = result.encode("ascii")
+ # NOTE: this seed value is NOT the same as the config string
+ result = (u("%s$sha1$%s") % (self.salt, rounds)).encode("ascii")
+ # NOTE: this algorithm is essentially PBKDF1, modified to use HMAC.
r = 0
while r < rounds:
result = _hmac_sha1(secret, result)
diff --git a/passlib/handlers/sha2_crypt.py b/passlib/handlers/sha2_crypt.py
index 13de342..4bcf3f2 100644
--- a/passlib/handlers/sha2_crypt.py
+++ b/passlib/handlers/sha2_crypt.py
@@ -11,7 +11,7 @@ from warnings import warn
from passlib.utils import classproperty, h64, safe_crypt, test_crypt, \
repeat_string, to_unicode
from passlib.utils.compat import b, bytes, byte_elem_value, irange, u, \
- uascii_to_str, unicode, lmap
+ uascii_to_str, unicode
import passlib.utils.handlers as uh
#pkg
#local
@@ -24,6 +24,7 @@ __all__ = [
# pure-python backend, used by both sha256_crypt & sha512_crypt
# when crypt.crypt() backend is not available.
#==============================================================
+_BNULL = b('\x00')
# pre-calculated offsets used to speed up C digest stage (see notes below).
# sequence generated using the following:
@@ -76,8 +77,9 @@ def _raw_sha2_crypt(pwd, salt, rounds, use_512=False):
if isinstance(pwd, unicode):
# XXX: not sure what official unicode policy is, using this as default
pwd = pwd.encode("utf-8")
- elif not isinstance(pwd, bytes):
- raise TypeError("password must be bytes or unicode")
+ assert isinstance(pwd, bytes)
+ if _BNULL in pwd:
+ raise uh.exc.NullPasswordError(sha512_crypt if use_512 else sha256_crypt)
pwd_len = len(pwd)
# validate rounds