diff -ur a/passlib/utils/__init__.py b/passlib/utils/__init__.py
--- a/passlib/utils/__init__.py	2019-11-19 11:41:26.000000000 -0800
+++ b/passlib/utils/__init__.py	2019-12-03 14:16:15.153791186 -0800
@@ -57,7 +57,7 @@
 )
 from passlib.exc import ExpectedStringError
 from passlib.utils.compat import (add_doc, join_bytes, join_byte_values,
-                                  join_byte_elems, irange, imap, PY3, u,
+                                  join_byte_elems, irange, imap, PY3, PYPY, u,
                                   join_unicode, unicode, byte_elem_value, nextgetter,
                                   unicode_or_bytes_types,
                                   get_method_function, suppress_cause)
@@ -776,23 +776,41 @@

     if PY3:
         def safe_crypt(secret, hash):
-            if isinstance(secret, bytes):
-                # Python 3's crypt() only accepts unicode, which is then
-                # encoding using utf-8 before passing to the C-level crypt().
-                # so we have to decode the secret.
-                orig = secret
+            if not PYPY:
+                if isinstance(secret, bytes):
+                    # Python 3's crypt() only accepts 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
+                    assert secret.encode("utf-8") == orig, \
+                                "utf-8 spec says this can't happen!"
+                if _NULL in secret:
+                    raise ValueError("null character in secret")
+            else:
+                if isinstance(secret, str):
+                    orig = secret
+                    try:
+                        secret = secret.encode("utf-8")
+                    except UnicodeEncodeError:
+                        return None
+                    assert secret.decode("utf-8") == orig, \
+                                "utf-8 spec says this can't happen!"
                 try:
-                    secret = secret.decode("utf-8")
+                    if _NULL in secret.decode("utf-8"):
+                        raise ValueError("null character in secret")
                 except UnicodeDecodeError:
                     return None
-                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")
             result = _crypt(secret, hash)
-            if not result or result[0] in _invalid_prefixes:
+            if PYPY and isinstance(result, bytes):
+                result = result.decode("utf-8")
+            if not result or result[0:1] in _invalid_prefixes:
                 return None
             return result
     else:
