summaryrefslogtreecommitdiff
path: root/passlib
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2020-10-03 15:18:27 -0400
committerEli Collins <elic@assurancetechnologies.com>2020-10-03 15:18:27 -0400
commit13330785701f099699e3b32fe8d60291df18b661 (patch)
tree8a3e43730822fc07d20c9ae96320bc53165d18bb /passlib
parentb252bf6b0ecfda46d81bb29b530b33466cec3cef (diff)
downloadpasslib-13330785701f099699e3b32fe8d60291df18b661.tar.gz
utils.safe_crypt(): turns out pypy3's crypt() *does* support raw bytes.
so instead of assuming safe_crypt() can only take UTF8 under py3, and anything under py2; code is now tied to "crypt_accepts_bytes" flag which does quick capability-detection when module loads. this updates the changes from rev 67c619208229
Diffstat (limited to 'passlib')
-rw-r--r--passlib/handlers/bcrypt.py4
-rw-r--r--passlib/utils/__init__.py65
2 files changed, 50 insertions, 19 deletions
diff --git a/passlib/handlers/bcrypt.py b/passlib/handlers/bcrypt.py
index 323f2a0..b595aaa 100644
--- a/passlib/handlers/bcrypt.py
+++ b/passlib/handlers/bcrypt.py
@@ -28,7 +28,7 @@ from passlib.crypto.digest import compile_hmac
from passlib.exc import PasslibHashWarning, PasslibSecurityWarning, PasslibSecurityError
from passlib.utils import safe_crypt, repeat_string, to_bytes, parse_version, \
rng, getrandstr, test_crypt, to_unicode, \
- utf8_truncate, utf8_repeat_string
+ utf8_truncate, utf8_repeat_string, crypt_accepts_bytes
from passlib.utils.binary import bcrypt64
from passlib.utils.compat import get_unbound_method_function
from passlib.utils.compat import u, uascii_to_str, unicode, str_to_uascii, PY3, error_from
@@ -773,7 +773,7 @@ class _OsCryptBackend(_BcryptCommon):
#: set flag to ensure _prepare_digest_args() doesn't create invalid utf8 string
#: when truncating bytes.
- _require_valid_utf8_bytes = PY3
+ _require_valid_utf8_bytes = not crypt_accepts_bytes
@classmethod
def _load_backend_mixin(mixin_cls, name, dryrun):
diff --git a/passlib/utils/__init__.py b/passlib/utils/__init__.py
index 386ef65..9f9f3ed 100644
--- a/passlib/utils/__init__.py
+++ b/passlib/utils/__init__.py
@@ -837,9 +837,10 @@ def is_safe_crypt_input(value):
test if value is safe to pass to crypt.crypt();
under PY3, can't pass non-UTF8 bytes to crypt.crypt.
"""
+ if crypt_accepts_bytes or not isinstance(value, bytes):
+ return True
try:
- if PY3 and isinstance(value, bytes):
- value.decode("utf-8")
+ value.decode("utf-8")
return True
except UnicodeDecodeError:
return False
@@ -863,24 +864,49 @@ else:
_invalid_prefixes = u("*:!")
if PY3:
+
+ # * pypy3 (as of v7.3.1) has a crypt which accepts bytes, or ASCII-only unicode.
+ # * whereas CPython3 (as of v3.9) has a crypt which doesn't take bytes,
+ # but accepts ANY unicode (which it always encodes to UTF8).
+ crypt_accepts_bytes = True
+ try:
+ _crypt(b"\xEE", "xx")
+ except TypeError:
+ # CPython will throw TypeError
+ crypt_accepts_bytes = False
+ except: # no pragma
+ # don't care about other errors this might throw,
+ # just want to see if we get past initial type-coercion step.
+ pass
+
def safe_crypt(secret, hash):
- if isinstance(secret, bytes):
- # Python 3's crypt() only accepts unicode, which is then
+ if crypt_accepts_bytes:
+ # PyPy3 -- all bytes accepted, but unicode encoded to ASCII,
+ # so handling that ourselves.
+ if isinstance(secret, unicode):
+ secret = secret.encode("utf-8")
+ if _BNULL in secret:
+ raise ValueError("null character in secret")
+ if isinstance(hash, unicode):
+ hash = hash.encode("ascii")
+ else:
+ # CPython3's crypt() doesn't take bytes, only unicode; unicode which is then
# encoding using utf-8 before passing to the C-level crypt().
# so we have to decode the secret.
- orig = secret
- try:
- secret = secret.decode("utf-8")
- except UnicodeDecodeError:
- return None
- # sanity check it encodes back to original byte string,
- # otherwise when crypt() does it's encoding, it'll hash the wrong bytes!
- assert secret.encode("utf-8") == orig, \
- "utf-8 spec says this can't happen!"
- if _NULL in secret:
- raise ValueError("null character in secret")
- if isinstance(hash, bytes):
- hash = hash.decode("ascii")
+ if isinstance(secret, bytes):
+ orig = secret
+ try:
+ secret = secret.decode("utf-8")
+ except UnicodeDecodeError:
+ return None
+ # sanity check it encodes back to original byte string,
+ # otherwise when crypt() does it's encoding, it'll hash the wrong bytes!
+ assert secret.encode("utf-8") == orig, \
+ "utf-8 spec says this can't happen!"
+ if _NULL in secret:
+ raise ValueError("null character in secret")
+ if isinstance(hash, bytes):
+ hash = hash.decode("ascii")
try:
result = _crypt(secret, hash)
except OSError:
@@ -897,6 +923,11 @@ else:
return None
return result
else:
+
+ #: see feature-detection in PY3 fork above
+ crypt_accepts_bytes = True
+
+ # Python 2 crypt handler
def safe_crypt(secret, hash):
if isinstance(secret, unicode):
secret = secret.encode("utf-8")