[Python-modules-commits] [python-bcrypt] 01/08: Import python-bcrypt_3.1.0.orig.tar.xz

Daniel Stender stender at moszumanska.debian.org
Mon Jul 25 21:39:59 UTC 2016


This is an automated email from the git hooks/post-receive script.

stender pushed a commit to branch master
in repository python-bcrypt.

commit e2cc8593ad39a9e6b019ffca79a6328820a84402
Author: Daniel Stender <stender at debian.org>
Date:   Mon Jul 25 22:59:13 2016 +0200

    Import python-bcrypt_3.1.0.orig.tar.xz
---
 .coveragerc                     |   5 +
 MANIFEST.in                     |   8 +
 PKG-INFO                        |  35 ++--
 README.rst                      |  33 +--
 src/_csrc/portable_endian.h     |  16 +-
 src/_csrc/pycabcrypt.h          |   3 +-
 src/_csrc/sha2.h                |   8 +-
 src/bcrypt.egg-info/PKG-INFO    |  35 ++--
 src/bcrypt.egg-info/SOURCES.txt |   5 +-
 src/bcrypt/__about__.py         |   2 +-
 src/bcrypt/__init__.py          |  43 +++-
 src/build_bcrypt.py             |   1 +
 tests/test_bcrypt.py            | 438 ++++++++++++++++++++++++++++++++++++++++
 tox.ini                         |  42 ++++
 14 files changed, 617 insertions(+), 57 deletions(-)

diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..1db160f
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,5 @@
+[run]
+branch = True
+source =
+    bcrypt
+    tests/
diff --git a/MANIFEST.in b/MANIFEST.in
index 622d66b..93a4480 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,11 @@
 include LICENSE README.rst
+
+include tox.ini .coveragerc
 include src/build_bcrypt.py
+
 recursive-include src/_csrc *
+recursive-include tests *.py
+
+exclude requirements.txt tasks.py .travis.yml
+
+prune .travis
diff --git a/PKG-INFO b/PKG-INFO
index 76f3950..fbb8815 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: bcrypt
-Version: 3.0.0
+Version: 3.1.0
 Summary: Modern password hashing for your software and your servers
 Home-page: https://github.com/pyca/bcrypt/
 Author: The Python Cryptographic Authority developers
@@ -28,7 +28,7 @@ Description: bcrypt
         
             $ pip install bcrypt
         
-        Note that bcrypt should build very easily on Linux provided you have a C compiler, headers for Python (if you’re not using pypy), and headers for the libffi libraries available on your system.
+        Note that bcrypt should build very easily on Linux provided you have a C compiler, headers for Python (if you're not using pypy), and headers for the libffi libraries available on your system.
         
         For Debian and Ubuntu, the following command will ensure that the required dependencies are installed:
         
@@ -45,22 +45,29 @@ Description: bcrypt
         Changelog
         =========
         
+        3.1.0
+        -----
+        * Added support for ``checkpw``, a convenience method for verifying a password.
+        * Ensure that you get a ``$2y$`` hash when you input a ``$2y$`` salt.
+        * Fixed a regression where ``$2a`` hashes were vulnerable to a wraparound bug.
+        * Fixed compilation under Alpine Linux.
+        
         3.0.0
         -----
         * Switched the C backend to code obtained from the OpenBSD project rather than
           openwall.
-        * Added support for `bcrypt_pbkdf` via the `kdf` function.
+        * Added support for ``bcrypt_pbkdf`` via the ``kdf`` function.
         
         2.0.0
         -----
-        * Added support for an adjustible prefix when calling `gensalt`.
+        * Added support for an adjustible prefix when calling ``gensalt``.
         * Switched to CFFI 1.0+
         
         Usage
         -----
         
-        Hashing
-        ~~~~~~~
+        Password Hashing
+        ~~~~~~~~~~~~~~~~
         
         Hashing and then later checking that a password matches the previous hashed
         password is very simple:
@@ -71,9 +78,9 @@ Description: bcrypt
             >>> password = b"super secret password"
             >>> # Hash a password for the first time, with a randomly-generated salt
             >>> hashed = bcrypt.hashpw(password, bcrypt.gensalt())
-            >>> # Check that a unhashed password matches one that has previously been
-            >>> #   hashed
-            >>> if bcrypt.hashpw(password, hashed) == hashed:
+            >>> # Check that an unhashed password matches one that has previously been
+            >>> # hashed
+            >>> if bcrypt.checkpw(password, hashed):
             ...     print("It Matches!")
             ... else:
             ...     print("It Does not Match :(")
@@ -81,7 +88,7 @@ Description: bcrypt
         KDF
         ~~~
         
-        As of 3.0.0 `bcrypt` now offers a `kdf` function which does `bcrypt_pbkdf`.
+        As of 3.0.0 ``bcrypt`` now offers a ``kdf`` function which does ``bcrypt_pbkdf``.
         This KDF is used in OpenSSH's newer encrypted private key format.
         
         .. code:: pycon
@@ -108,7 +115,7 @@ Description: bcrypt
             >>> hashed = bcrypt.hashpw(password, bcrypt.gensalt(14))
             >>> # Check that a unhashed password matches one that has previously been
             >>> #   hashed
-            >>> if bcrypt.hashpw(password, hashed) == hashed:
+            >>> if bcrypt.checkpw(password, hashed):
             ...     print("It Matches!")
             ... else:
             ...     print("It Does not Match :(")
@@ -121,10 +128,10 @@ Description: bcrypt
         libraries you'll remain compatible with. To adjust this, pass either ``2a`` or
         ``2b`` (the default) to ``bcrypt.gensalt(prefix=b"2b")`` as a bytes object.
         
-        As of 3.0.0 the `$2y$` prefix is still supported in `hashpw` but deprecated.
+        As of 3.0.0 the ``$2y$`` prefix is still supported in ``hashpw`` but deprecated.
         
-        Maxmimum Password Length
-        ~~~~~~~~~~~~~~~~~~~~~~~~
+        Maximum Password Length
+        ~~~~~~~~~~~~~~~~~~~~~~~
         
         The bcrypt algorithm only handles passwords up to 72 characters, any characters
         beyond that are ignored. To work around this, a common approach is to hash a
diff --git a/README.rst b/README.rst
index 9519ff4..bd5e967 100644
--- a/README.rst
+++ b/README.rst
@@ -20,7 +20,7 @@ To install bcrypt, simply:
 
     $ pip install bcrypt
 
-Note that bcrypt should build very easily on Linux provided you have a C compiler, headers for Python (if you’re not using pypy), and headers for the libffi libraries available on your system.
+Note that bcrypt should build very easily on Linux provided you have a C compiler, headers for Python (if you're not using pypy), and headers for the libffi libraries available on your system.
 
 For Debian and Ubuntu, the following command will ensure that the required dependencies are installed:
 
@@ -37,22 +37,29 @@ For Fedora and RHEL-derivatives, the following command will ensure that the requ
 Changelog
 =========
 
+3.1.0
+-----
+* Added support for ``checkpw``, a convenience method for verifying a password.
+* Ensure that you get a ``$2y$`` hash when you input a ``$2y$`` salt.
+* Fixed a regression where ``$2a`` hashes were vulnerable to a wraparound bug.
+* Fixed compilation under Alpine Linux.
+
 3.0.0
 -----
 * Switched the C backend to code obtained from the OpenBSD project rather than
   openwall.
-* Added support for `bcrypt_pbkdf` via the `kdf` function.
+* Added support for ``bcrypt_pbkdf`` via the ``kdf`` function.
 
 2.0.0
 -----
-* Added support for an adjustible prefix when calling `gensalt`.
+* Added support for an adjustible prefix when calling ``gensalt``.
 * Switched to CFFI 1.0+
 
 Usage
 -----
 
-Hashing
-~~~~~~~
+Password Hashing
+~~~~~~~~~~~~~~~~
 
 Hashing and then later checking that a password matches the previous hashed
 password is very simple:
@@ -63,9 +70,9 @@ password is very simple:
     >>> password = b"super secret password"
     >>> # Hash a password for the first time, with a randomly-generated salt
     >>> hashed = bcrypt.hashpw(password, bcrypt.gensalt())
-    >>> # Check that a unhashed password matches one that has previously been
-    >>> #   hashed
-    >>> if bcrypt.hashpw(password, hashed) == hashed:
+    >>> # Check that an unhashed password matches one that has previously been
+    >>> # hashed
+    >>> if bcrypt.checkpw(password, hashed):
     ...     print("It Matches!")
     ... else:
     ...     print("It Does not Match :(")
@@ -73,7 +80,7 @@ password is very simple:
 KDF
 ~~~
 
-As of 3.0.0 `bcrypt` now offers a `kdf` function which does `bcrypt_pbkdf`.
+As of 3.0.0 ``bcrypt`` now offers a ``kdf`` function which does ``bcrypt_pbkdf``.
 This KDF is used in OpenSSH's newer encrypted private key format.
 
 .. code:: pycon
@@ -100,7 +107,7 @@ the work factor merely pass the desired number of rounds to
     >>> hashed = bcrypt.hashpw(password, bcrypt.gensalt(14))
     >>> # Check that a unhashed password matches one that has previously been
     >>> #   hashed
-    >>> if bcrypt.hashpw(password, hashed) == hashed:
+    >>> if bcrypt.checkpw(password, hashed):
     ...     print("It Matches!")
     ... else:
     ...     print("It Does not Match :(")
@@ -113,10 +120,10 @@ Another one of bcrypt's features is an adjustable prefix to let you define what
 libraries you'll remain compatible with. To adjust this, pass either ``2a`` or
 ``2b`` (the default) to ``bcrypt.gensalt(prefix=b"2b")`` as a bytes object.
 
-As of 3.0.0 the `$2y$` prefix is still supported in `hashpw` but deprecated.
+As of 3.0.0 the ``$2y$`` prefix is still supported in ``hashpw`` but deprecated.
 
-Maxmimum Password Length
-~~~~~~~~~~~~~~~~~~~~~~~~
+Maximum Password Length
+~~~~~~~~~~~~~~~~~~~~~~~
 
 The bcrypt algorithm only handles passwords up to 72 characters, any characters
 beyond that are ignored. To work around this, a common approach is to hash a
diff --git a/src/_csrc/portable_endian.h b/src/_csrc/portable_endian.h
index e90b203..848803b 100644
--- a/src/_csrc/portable_endian.h
+++ b/src/_csrc/portable_endian.h
@@ -15,13 +15,21 @@
 
 #if defined(__linux__) || defined(__CYGWIN__)
 /* Define necessary macros for the header to expose all fields. */
-#   define _BSD_SOURCE
-#   define __USE_BSD
-#   define _DEFAULT_SOURCE
+#   if !defined(_BSD_SOURCE)
+#       define _BSD_SOURCE
+#   endif
+#   if !defined(__USE_BSD)
+#       define __USE_BSD
+#   endif
+#   if !defined(_DEFAULT_SOURCE)
+#       define _DEFAULT_SOURCE
+#   endif
 #   include <endian.h>
 #   include <features.h>
 /* See http://linux.die.net/man/3/endian */
-#   if !defined(__GLIBC__) || !defined(__GLIBC_MINOR__) || ((__GLIBC__ < 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ < 9)))
+#   if defined(htobe16) && defined(htole16) && defined(be16toh) && defined(le16toh) && defined(htobe32) && defined(htole32) && defined(be32toh) && defined(htole32) && defined(htobe64) && defined(htole64) && defined(be64) && defined(le64)
+/* Do nothing. The macros we need already exist. */
+#   elif !defined(__GLIBC__) || !defined(__GLIBC_MINOR__) || ((__GLIBC__ < 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ < 9)))
 #       include <arpa/inet.h>
 #       if defined(__BYTE_ORDER) && (__BYTE_ORDER == __LITTLE_ENDIAN)
 #           define htobe16(x) htons(x)
diff --git a/src/_csrc/pycabcrypt.h b/src/_csrc/pycabcrypt.h
index 0c089e7..c1a8422 100644
--- a/src/_csrc/pycabcrypt.h
+++ b/src/_csrc/pycabcrypt.h
@@ -14,8 +14,6 @@ typedef unsigned long long uint64_t;
 typedef uint64_t u_int64_t;
 #define snprintf _snprintf
 #define __attribute__(unused)
-#define __BEGIN_DECLS
-#define __END_DECLS
 #else
 #include <stdint.h>
 #endif
@@ -26,3 +24,4 @@ typedef uint64_t u_int64_t;
 int bcrypt_hashpass(const char *key, const char *salt, char *encrypted, size_t encryptedlen);
 int encode_base64(char *, const u_int8_t *, size_t);
 int timingsafe_bcmp(const void *b1, const void *b2, size_t n);
+int bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltlen, uint8_t *key, size_t keylen, unsigned int rounds);
diff --git a/src/_csrc/sha2.h b/src/_csrc/sha2.h
index e487cfc..31b3830 100644
--- a/src/_csrc/sha2.h
+++ b/src/_csrc/sha2.h
@@ -60,7 +60,9 @@ typedef struct _SHA2_CTX {
 	u_int8_t	buffer[SHA512_BLOCK_LENGTH];
 } SHA2_CTX;
 
-__BEGIN_DECLS
+#ifdef __cplusplus
+extern "C" {
+#endif
 void SHA256Init(SHA2_CTX *);
 void SHA256Update(SHA2_CTX *, const void *, size_t)
 	__attribute__((__bounded__(__string__,2,3)));
@@ -78,6 +80,8 @@ void SHA512Update(SHA2_CTX *, const void *, size_t)
 	__attribute__((__bounded__(__string__,2,3)));
 void SHA512Final(u_int8_t[SHA512_DIGEST_LENGTH], SHA2_CTX *)
 	__attribute__((__bounded__(__minbytes__,1,SHA512_DIGEST_LENGTH)));
-__END_DECLS
+#ifdef __cplusplus
+}
+#endif
 
 #endif /* _SHA2_H */
diff --git a/src/bcrypt.egg-info/PKG-INFO b/src/bcrypt.egg-info/PKG-INFO
index 76f3950..fbb8815 100644
--- a/src/bcrypt.egg-info/PKG-INFO
+++ b/src/bcrypt.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: bcrypt
-Version: 3.0.0
+Version: 3.1.0
 Summary: Modern password hashing for your software and your servers
 Home-page: https://github.com/pyca/bcrypt/
 Author: The Python Cryptographic Authority developers
@@ -28,7 +28,7 @@ Description: bcrypt
         
             $ pip install bcrypt
         
-        Note that bcrypt should build very easily on Linux provided you have a C compiler, headers for Python (if you’re not using pypy), and headers for the libffi libraries available on your system.
+        Note that bcrypt should build very easily on Linux provided you have a C compiler, headers for Python (if you're not using pypy), and headers for the libffi libraries available on your system.
         
         For Debian and Ubuntu, the following command will ensure that the required dependencies are installed:
         
@@ -45,22 +45,29 @@ Description: bcrypt
         Changelog
         =========
         
+        3.1.0
+        -----
+        * Added support for ``checkpw``, a convenience method for verifying a password.
+        * Ensure that you get a ``$2y$`` hash when you input a ``$2y$`` salt.
+        * Fixed a regression where ``$2a`` hashes were vulnerable to a wraparound bug.
+        * Fixed compilation under Alpine Linux.
+        
         3.0.0
         -----
         * Switched the C backend to code obtained from the OpenBSD project rather than
           openwall.
-        * Added support for `bcrypt_pbkdf` via the `kdf` function.
+        * Added support for ``bcrypt_pbkdf`` via the ``kdf`` function.
         
         2.0.0
         -----
-        * Added support for an adjustible prefix when calling `gensalt`.
+        * Added support for an adjustible prefix when calling ``gensalt``.
         * Switched to CFFI 1.0+
         
         Usage
         -----
         
-        Hashing
-        ~~~~~~~
+        Password Hashing
+        ~~~~~~~~~~~~~~~~
         
         Hashing and then later checking that a password matches the previous hashed
         password is very simple:
@@ -71,9 +78,9 @@ Description: bcrypt
             >>> password = b"super secret password"
             >>> # Hash a password for the first time, with a randomly-generated salt
             >>> hashed = bcrypt.hashpw(password, bcrypt.gensalt())
-            >>> # Check that a unhashed password matches one that has previously been
-            >>> #   hashed
-            >>> if bcrypt.hashpw(password, hashed) == hashed:
+            >>> # Check that an unhashed password matches one that has previously been
+            >>> # hashed
+            >>> if bcrypt.checkpw(password, hashed):
             ...     print("It Matches!")
             ... else:
             ...     print("It Does not Match :(")
@@ -81,7 +88,7 @@ Description: bcrypt
         KDF
         ~~~
         
-        As of 3.0.0 `bcrypt` now offers a `kdf` function which does `bcrypt_pbkdf`.
+        As of 3.0.0 ``bcrypt`` now offers a ``kdf`` function which does ``bcrypt_pbkdf``.
         This KDF is used in OpenSSH's newer encrypted private key format.
         
         .. code:: pycon
@@ -108,7 +115,7 @@ Description: bcrypt
             >>> hashed = bcrypt.hashpw(password, bcrypt.gensalt(14))
             >>> # Check that a unhashed password matches one that has previously been
             >>> #   hashed
-            >>> if bcrypt.hashpw(password, hashed) == hashed:
+            >>> if bcrypt.checkpw(password, hashed):
             ...     print("It Matches!")
             ... else:
             ...     print("It Does not Match :(")
@@ -121,10 +128,10 @@ Description: bcrypt
         libraries you'll remain compatible with. To adjust this, pass either ``2a`` or
         ``2b`` (the default) to ``bcrypt.gensalt(prefix=b"2b")`` as a bytes object.
         
-        As of 3.0.0 the `$2y$` prefix is still supported in `hashpw` but deprecated.
+        As of 3.0.0 the ``$2y$`` prefix is still supported in ``hashpw`` but deprecated.
         
-        Maxmimum Password Length
-        ~~~~~~~~~~~~~~~~~~~~~~~~
+        Maximum Password Length
+        ~~~~~~~~~~~~~~~~~~~~~~~
         
         The bcrypt algorithm only handles passwords up to 72 characters, any characters
         beyond that are ignored. To work around this, a common approach is to hash a
diff --git a/src/bcrypt.egg-info/SOURCES.txt b/src/bcrypt.egg-info/SOURCES.txt
index ede6ff9..44faeac 100644
--- a/src/bcrypt.egg-info/SOURCES.txt
+++ b/src/bcrypt.egg-info/SOURCES.txt
@@ -1,7 +1,9 @@
+.coveragerc
 LICENSE
 MANIFEST.in
 README.rst
 setup.py
+tox.ini
 src/build_bcrypt.py
 src/_csrc/bcrypt.c
 src/_csrc/bcrypt_pbkdf.c
@@ -19,4 +21,5 @@ src/bcrypt.egg-info/SOURCES.txt
 src/bcrypt.egg-info/dependency_links.txt
 src/bcrypt.egg-info/not-zip-safe
 src/bcrypt.egg-info/requires.txt
-src/bcrypt.egg-info/top_level.txt
\ No newline at end of file
+src/bcrypt.egg-info/top_level.txt
+tests/test_bcrypt.py
\ No newline at end of file
diff --git a/src/bcrypt/__about__.py b/src/bcrypt/__about__.py
index e7fc766..c7674a6 100644
--- a/src/bcrypt/__about__.py
+++ b/src/bcrypt/__about__.py
@@ -26,7 +26,7 @@ __title__ = "bcrypt"
 __summary__ = "Modern password hashing for your software and your servers"
 __uri__ = "https://github.com/pyca/bcrypt/"
 
-__version__ = "3.0.0"
+__version__ = "3.1.0"
 
 __author__ = "The Python Cryptographic Authority developers"
 __email__ = "cryptography-dev at python.org"
diff --git a/src/bcrypt/__init__.py b/src/bcrypt/__init__.py
index ad44e93..abc9d75 100644
--- a/src/bcrypt/__init__.py
+++ b/src/bcrypt/__init__.py
@@ -39,10 +39,6 @@ __all__ = [
 _normalize_re = re.compile(b"^\$2y\$")
 
 
-def _normalize_prefix(salt):
-    return _normalize_re.sub(b"$2b$", salt)
-
-
 def gensalt(rounds=12, prefix=b"2b"):
     if prefix not in (b"2a", b"2b"):
         raise ValueError("Supported prefixes are b'2a' or b'2b'")
@@ -67,7 +63,21 @@ def hashpw(password, salt):
     if b"\x00" in password:
         raise ValueError("password may not contain NUL bytes")
 
-    salt = _normalize_prefix(salt)
+    # bcrypt originally suffered from a wraparound bug:
+    # http://www.openwall.com/lists/oss-security/2012/01/02/4
+    # This bug was corrected in the OpenBSD source by truncating inputs to 72
+    # bytes on the updated prefix $2b$, but leaving $2a$ unchanged for
+    # compatibility. However, pyca/bcrypt 2.0.0 *did* correctly truncate inputs
+    # on $2a$, so we do it here to preserve compatibility with 2.0.0
+    password = password[:72]
+
+    # When the original 8bit bug was found the original library we supported
+    # added a new prefix, $2y$, that fixes it. This prefix is exactly the same
+    # as the $2b$ prefix added by OpenBSD other than the name. Since the
+    # OpenBSD library does not support the $2y$ prefix, if the salt given to us
+    # is for the $2y$ prefix, we'll just mugne it so that it's a $2b$ prior to
+    # passing it into the C library.
+    original_salt, salt = salt, _normalize_re.sub(b"$2b$", salt)
 
     hashed = _bcrypt.ffi.new("unsigned char[]", 128)
     retval = _bcrypt.lib.bcrypt_hashpass(password, salt, hashed, len(hashed))
@@ -75,7 +85,28 @@ def hashpw(password, salt):
     if retval != 0:
         raise ValueError("Invalid salt")
 
-    return _bcrypt.ffi.string(hashed)
+    # Now that we've gotten our hashed password, we want to ensure that the
+    # prefix we return is the one that was passed in, so we'll use the prefix
+    # from the original salt and concatenate that with the return value (minus
+    # the return value's prefix). This will ensure that if someone passed in a
+    # salt with a $2y$ prefix, that they get back a hash with a $2y$ prefix
+    # even though we munged it to $2b$.
+    return original_salt[:4] + _bcrypt.ffi.string(hashed)[4:]
+
+
+def checkpw(password, hashed_password):
+    if (isinstance(password, six.text_type) or
+            isinstance(hashed_password, six.text_type)):
+        raise TypeError("Unicode-objects must be encoded before checking")
+
+    if b"\x00" in password or b"\x00" in hashed_password:
+        raise ValueError(
+            "password and hashed_password may not contain NUL bytes"
+        )
+
+    ret = hashpw(password, hashed_password)
+
+    return _bcrypt.lib.timingsafe_bcmp(ret, hashed_password, len(ret)) == 0
 
 
 def kdf(password, salt, desired_key_bytes, rounds):
diff --git a/src/build_bcrypt.py b/src/build_bcrypt.py
index e7aca4c..3eec35c 100644
--- a/src/build_bcrypt.py
+++ b/src/build_bcrypt.py
@@ -25,6 +25,7 @@ int bcrypt_hashpass(const char *, const char *, char *, size_t);
 int encode_base64(char *, const uint8_t *, size_t);
 int bcrypt_pbkdf(const char *, size_t, const uint8_t *, size_t,
                  uint8_t *, size_t, unsigned int);
+int timingsafe_bcmp(const void *, const void *, size_t);
 """)
 
 ffi.set_source(
diff --git a/tests/test_bcrypt.py b/tests/test_bcrypt.py
new file mode 100644
index 0000000..d9bde72
--- /dev/null
+++ b/tests/test_bcrypt.py
@@ -0,0 +1,438 @@
+import os
+
+import pytest
+
+import six
+
+import bcrypt
+
+
+_test_vectors = [
+    (
+        b"Kk4DQuMMfZL9o",
+        b"$2b$04$cVWp4XaNU8a4v1uMRum2SO",
+        b"$2b$04$cVWp4XaNU8a4v1uMRum2SO026BWLIoQMD/TXg5uZV.0P.uO8m3YEm",
+    ),
+    (
+        b"9IeRXmnGxMYbs",
+        b"$2b$04$pQ7gRO7e6wx/936oXhNjrO",
+        b"$2b$04$pQ7gRO7e6wx/936oXhNjrOUNOHL1D0h1N2IDbJZYs.1ppzSof6SPy",
+    ),
+    (
+        b"xVQVbwa1S0M8r",
+        b"$2b$04$SQe9knOzepOVKoYXo9xTte",
+        b"$2b$04$SQe9knOzepOVKoYXo9xTteNYr6MBwVz4tpriJVe3PNgYufGIsgKcW",
+    ),
+    (
+        b"Zfgr26LWd22Za",
+        b"$2b$04$eH8zX.q5Q.j2hO1NkVYJQO",
+        b"$2b$04$eH8zX.q5Q.j2hO1NkVYJQOM6KxntS/ow3.YzVmFrE4t//CoF4fvne",
+    ),
+    (
+        b"Tg4daC27epFBE",
+        b"$2b$04$ahiTdwRXpUG2JLRcIznxc.",
+        b"$2b$04$ahiTdwRXpUG2JLRcIznxc.s1.ydaPGD372bsGs8NqyYjLY1inG5n2",
+    ),
+    (
+        b"xhQPMmwh5ALzW",
+        b"$2b$04$nQn78dV0hGHf5wUBe0zOFu",
+        b"$2b$04$nQn78dV0hGHf5wUBe0zOFu8n07ZbWWOKoGasZKRspZxtt.vBRNMIy",
+    ),
+    (
+        b"59je8h5Gj71tg",
+        b"$2b$04$cvXudZ5ugTg95W.rOjMITu",
+        b"$2b$04$cvXudZ5ugTg95W.rOjMITuM1jC0piCl3zF5cmGhzCibHZrNHkmckG",
+    ),
+    (
+        b"wT4fHJa2N9WSW",
+        b"$2b$04$YYjtiq4Uh88yUsExO0RNTu",
+        b"$2b$04$YYjtiq4Uh88yUsExO0RNTuEJ.tZlsONac16A8OcLHleWFjVawfGvO",
+    ),
+    (
+        b"uSgFRnQdOgm4S",
+        b"$2b$04$WLTjgY/pZSyqX/fbMbJzf.",
+        b"$2b$04$WLTjgY/pZSyqX/fbMbJzf.qxCeTMQOzgL.CimRjMHtMxd/VGKojMu",
+    ),
+    (
+        b"tEPtJZXur16Vg",
+        b"$2b$04$2moPs/x/wnCfeQ5pCheMcu",
+        b"$2b$04$2moPs/x/wnCfeQ5pCheMcuSJQ/KYjOZG780UjA/SiR.KsYWNrC7SG",
+    ),
+    (
+        b"vvho8C6nlVf9K",
+        b"$2b$04$HrEYC/AQ2HS77G78cQDZQ.",
+        b"$2b$04$HrEYC/AQ2HS77G78cQDZQ.r44WGcruKw03KHlnp71yVQEwpsi3xl2",
+    ),
+    (
+        b"5auCCY9by0Ruf",
+        b"$2b$04$vVYgSTfB8KVbmhbZE/k3R.",
+        b"$2b$04$vVYgSTfB8KVbmhbZE/k3R.ux9A0lJUM4CZwCkHI9fifke2.rTF7MG",
+    ),
+    (
+        b"GtTkR6qn2QOZW",
+        b"$2b$04$JfoNrR8.doieoI8..F.C1O",
+        b"$2b$04$JfoNrR8.doieoI8..F.C1OQgwE3uTeuardy6lw0AjALUzOARoyf2m",
+    ),
+    (
+        b"zKo8vdFSnjX0f",
+        b"$2b$04$HP3I0PUs7KBEzMBNFw7o3O",
+        b"$2b$04$HP3I0PUs7KBEzMBNFw7o3O7f/uxaZU7aaDot1quHMgB2yrwBXsgyy",
+    ),
+    (
+        b"I9VfYlacJiwiK",
+        b"$2b$04$xnFVhJsTzsFBTeP3PpgbMe",
+        b"$2b$04$xnFVhJsTzsFBTeP3PpgbMeMREb6rdKV9faW54Sx.yg9plf4jY8qT6",
+    ),
+    (
+        b"VFPO7YXnHQbQO",
+        b"$2b$04$WQp9.igoLqVr6Qk70mz6xu",
+        b"$2b$04$WQp9.igoLqVr6Qk70mz6xuRxE0RttVXXdukpR9N54x17ecad34ZF6",
+    ),
+    (
+        b"VDx5BdxfxstYk",
+        b"$2b$04$xgZtlonpAHSU/njOCdKztO",
+        b"$2b$04$xgZtlonpAHSU/njOCdKztOPuPFzCNVpB4LGicO4/OGgHv.uKHkwsS",
+    ),
+    (
+        b"dEe6XfVGrrfSH",
+        b"$2b$04$2Siw3Nv3Q/gTOIPetAyPr.",
+        b"$2b$04$2Siw3Nv3Q/gTOIPetAyPr.GNj3aO0lb1E5E9UumYGKjP9BYqlNWJe",
+    ),
+    (
+        b"cTT0EAFdwJiLn",
+        b"$2b$04$7/Qj7Kd8BcSahPO4khB8me",
+        b"$2b$04$7/Qj7Kd8BcSahPO4khB8me4ssDJCW3r4OGYqPF87jxtrSyPj5cS5m",
+    ),
+    (
+        b"J8eHUDuxBB520",
+        b"$2b$04$VvlCUKbTMjaxaYJ.k5juoe",
+        b"$2b$04$VvlCUKbTMjaxaYJ.k5juoecpG/7IzcH1AkmqKi.lIZMVIOLClWAk.",
+    ),
+    (
+        b"U*U",
+        b"$2a$05$CCCCCCCCCCCCCCCCCCCCC.",
+        b"$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW",
+    ),
+    (
+        b"U*U*",
+        b"$2a$05$CCCCCCCCCCCCCCCCCCCCC.",
+        b"$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK",
+    ),
+    (
+        b"U*U*U",
+        b"$2a$05$XXXXXXXXXXXXXXXXXXXXXO",
+        b"$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a",
+    ),
+    (
+        b"0123456789abcdefghijklmnopqrstuvwxyz"
+        b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+        b"chars after 72 are ignored",
+        b"$2a$05$abcdefghijklmnopqrstuu",
+        b"$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui",
+    ),
+    (
+        b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+        b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+        b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+        b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+        b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+        b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
+        b"chars after 72 are ignored as usual",
+        b"$2a$05$/OK.fbVrR/bpIqNJ5ianF.",
+        b"$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6"
+    ),
+    (
+        b"\xa3",
+        b"$2a$05$/OK.fbVrR/bpIqNJ5ianF.",
+        b"$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq"
+    ),
+]
+
+_2y_test_vectors = [
+    (
+        b"\xa3",
+        b"$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq",
+        b"$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq",
+    ),
+    (
+        b"\xff\xff\xa3",
+        b"$2y$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e",
+        b"$2y$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e",
+    ),
+]
+
+
+def test_gensalt_basic(monkeypatch):
+    monkeypatch.setattr(os, "urandom", lambda n: b"0000000000000000")
+    assert bcrypt.gensalt() == b"$2b$12$KB.uKB.uKB.uKB.uKB.uK."
+
+
+ at pytest.mark.parametrize(("rounds", "expected"), [
+    (4, b"$2b$04$KB.uKB.uKB.uKB.uKB.uK."),
+    (5, b"$2b$05$KB.uKB.uKB.uKB.uKB.uK."),
+    (6, b"$2b$06$KB.uKB.uKB.uKB.uKB.uK."),
+    (7, b"$2b$07$KB.uKB.uKB.uKB.uKB.uK."),
+    (8, b"$2b$08$KB.uKB.uKB.uKB.uKB.uK."),
+    (9, b"$2b$09$KB.uKB.uKB.uKB.uKB.uK."),
+    (10, b"$2b$10$KB.uKB.uKB.uKB.uKB.uK."),
+    (11, b"$2b$11$KB.uKB.uKB.uKB.uKB.uK."),
+    (12, b"$2b$12$KB.uKB.uKB.uKB.uKB.uK."),
+    (13, b"$2b$13$KB.uKB.uKB.uKB.uKB.uK."),
+    (14, b"$2b$14$KB.uKB.uKB.uKB.uKB.uK."),
+    (15, b"$2b$15$KB.uKB.uKB.uKB.uKB.uK."),
+    (16, b"$2b$16$KB.uKB.uKB.uKB.uKB.uK."),
+    (17, b"$2b$17$KB.uKB.uKB.uKB.uKB.uK."),
+    (18, b"$2b$18$KB.uKB.uKB.uKB.uKB.uK."),
+    (19, b"$2b$19$KB.uKB.uKB.uKB.uKB.uK."),
+    (20, b"$2b$20$KB.uKB.uKB.uKB.uKB.uK."),
+    (21, b"$2b$21$KB.uKB.uKB.uKB.uKB.uK."),
+    (22, b"$2b$22$KB.uKB.uKB.uKB.uKB.uK."),
+    (23, b"$2b$23$KB.uKB.uKB.uKB.uKB.uK."),
+    (24, b"$2b$24$KB.uKB.uKB.uKB.uKB.uK."),
+])
+def test_gensalt_rounds_valid(rounds, expected, monkeypatch):
+    monkeypatch.setattr(os, "urandom", lambda n: b"0000000000000000")
+    assert bcrypt.gensalt(rounds) == expected
+
+
+ at pytest.mark.parametrize("rounds", list(range(1, 4)))
+def test_gensalt_rounds_invalid(rounds):
+    with pytest.raises(ValueError):
+        bcrypt.gensalt(rounds)
+
+
+def test_gensalt_bad_prefix():
+    with pytest.raises(ValueError):
+        bcrypt.gensalt(prefix="bad")
+
+
+def test_gensalt_2a_prefix(monkeypatch):
+    monkeypatch.setattr(os, "urandom", lambda n: b"0000000000000000")
+    assert bcrypt.gensalt(prefix=b"2a") == b"$2a$12$KB.uKB.uKB.uKB.uKB.uK."
+
+
+ at pytest.mark.parametrize(("password", "salt", "hashed"), _test_vectors)
+def test_hashpw_new(password, salt, hashed):
+    assert bcrypt.hashpw(password, salt) == hashed
+
+
+ at pytest.mark.parametrize(("password", "salt", "hashed"), _test_vectors)
+def test_checkpw(password, salt, hashed):
+    assert bcrypt.checkpw(password, hashed) is True
+
+
+ at pytest.mark.parametrize(("password", "salt", "hashed"), _test_vectors)
+def test_hashpw_existing(password, salt, hashed):
+    assert bcrypt.hashpw(password, hashed) == hashed
+
+
+ at pytest.mark.parametrize(("password", "hashed", "expected"), _2y_test_vectors)
+def test_hashpw_2y_prefix(password, hashed, expected):
+    assert bcrypt.hashpw(password, hashed) == expected
+
+
+ at pytest.mark.parametrize(("password", "hashed", "expected"), _2y_test_vectors)
+def test_checkpw_2y_prefix(password, hashed, expected):
+    assert bcrypt.checkpw(password, hashed) is True
+
+
+def test_hashpw_invalid():
+    with pytest.raises(ValueError):
+        bcrypt.hashpw(b"password", b"$2z$04$cVWp4XaNU8a4v1uMRum2SO")
+
+
+def test_checkpw_wrong_password():
+    assert bcrypt.checkpw(
+        b"badpass",
+        b"$2b$04$2Siw3Nv3Q/gTOIPetAyPr.GNj3aO0lb1E5E9UumYGKjP9BYqlNWJe"
+    ) is False
+
+
+def test_checkpw_bad_salt():
+    with pytest.raises(ValueError):
+        bcrypt.checkpw(
+            b"badpass",
+            b"$2b$04$?Siw3Nv3Q/gTOIPetAyPr.GNj3aO0lb1E5E9UumYGKjP9BYqlNWJe"
+        )
+
+
+def test_checkpw_str_password():
+    with pytest.raises(TypeError):
+        bcrypt.checkpw(
+            six.text_type("password"),
+            b"$2b$04$cVWp4XaNU8a4v1uMRum2SO",
+        )
+
+
+def test_checkpw_str_salt():
+    with pytest.raises(TypeError):
+        bcrypt.checkpw(
+            b"password",
+            six.text_type("$2b$04$cVWp4XaNU8a4v1uMRum2SO"),
+        )
+
+
+def test_hashpw_str_password():
+    with pytest.raises(TypeError):
+        bcrypt.hashpw(
+            six.text_type("password"),
+            b"$2b$04$cVWp4XaNU8a4v1uMRum2SO",
+        )
+
+
+def test_hashpw_str_salt():
+    with pytest.raises(TypeError):
+        bcrypt.hashpw(
+            b"password",
+            six.text_type("$2b$04$cVWp4XaNU8a4v1uMRum2SO"),
+        )
+
+
+def test_checkpw_nul_byte():
+    with pytest.raises(ValueError):
+        bcrypt.checkpw(
+            b"abc\0def",
+            b"$2b$04$2Siw3Nv3Q/gTOIPetAyPr.GNj3aO0lb1E5E9UumYGKjP9BYqlNWJe"
+        )
+
+    with pytest.raises(ValueError):
+        bcrypt.checkpw(
+            b"abcdef",
+            b"$2b$04$2S\0w3Nv3Q/gTOIPetAyPr.GNj3aO0lb1E5E9UumYGKjP9BYqlNWJe"
+        )
+
+
+def test_hashpw_nul_byte():
+    salt = bcrypt.gensalt(4)
+    with pytest.raises(ValueError):
+        bcrypt.hashpw(b"abc\0def", salt)
+
+
+ at pytest.mark.parametrize(
+    ("rounds", "password", "salt", "expected"),
+    [[
+        4, b"password", b"salt",
+        b"\x5b\xbf\x0c\xc2\x93\x58\x7f\x1c\x36\x35\x55\x5c\x27\x79\x65\x98"
+        b"\xd4\x7e\x57\x90\x71\xbf\x42\x7e\x9d\x8f\xbe\x84\x2a\xba\x34\xd9"
+    ], [
+        4, b"password", b"\x00",
+        b"\xc1\x2b\x56\x62\x35\xee\xe0\x4c\x21\x25\x98\x97\x0a\x57\x9a\x67"
+    ], [
+        4, b"\x00", b"salt",
+        b"\x60\x51\xbe\x18\xc2\xf4\xf8\x2c\xbf\x0e\xfe\xe5\x47\x1b\x4b\xb9"
+    ], [
+        # nul bytes in password and string
+        4, b"password\x00", b"salt\x00",
+        b"\x74\x10\xe4\x4c\xf4\xfa\x07\xbf\xaa\xc8\xa9\x28\xb1\x72\x7f\xac"
+        b"\x00\x13\x75\xe7\xbf\x73\x84\x37\x0f\x48\xef\xd1\x21\x74\x30\x50"
+    ], [
+        4, b"pass\x00wor", b"sa\0l",
+        b"\xc2\xbf\xfd\x9d\xb3\x8f\x65\x69\xef\xef\x43\x72\xf4\xde\x83\xc0"
+    ], [
+        4, b"pass\x00word", b"sa\0lt",
+        b"\x4b\xa4\xac\x39\x25\xc0\xe8\xd7\xf0\xcd\xb6\xbb\x16\x84\xa5\x6f"
+    ], [
+        # bigger key
+        8, b"password", b"salt",
+        b"\xe1\x36\x7e\xc5\x15\x1a\x33\xfa\xac\x4c\xc1\xc1\x44\xcd\x23\xfa"
+        b"\x15\xd5\x54\x84\x93\xec\xc9\x9b\x9b\x5d\x9c\x0d\x3b\x27\xbe\xc7"
+        b"\x62\x27\xea\x66\x08\x8b\x84\x9b\x20\xab\x7a\xa4\x78\x01\x02\x46"
+        b"\xe7\x4b\xba\x51\x72\x3f\xef\xa9\xf9\x47\x4d\x65\x08\x84\x5e\x8d"
+    ], [
+        # more rounds
+        42, b"password", b"salt",
+        b"\x83\x3c\xf0\xdc\xf5\x6d\xb6\x56\x08\xe8\xf0\xdc\x0c\xe8\x82\xbd"
+    ], [
+        # longer password
+        8,
+        b"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do "
+        b"eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut "
+        b"enim ad minim veniam, quis nostrud exercitation ullamco laboris "
+        b"nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor "
+        b"in reprehenderit in voluptate velit esse cillum dolore eu fugiat "
+        b"nulla pariatur. Excepteur sint occaecat cupidatat non proident, "
+        b"sunt in culpa qui officia deserunt mollit anim id est laborum.",
+        b"salis\x00",
+        b"\x10\x97\x8b\x07\x25\x3d\xf5\x7f\x71\xa1\x62\xeb\x0e\x8a\xd3\x0a"
+    ], [
+        # "unicode"
+        8,
+        b"\x0d\xb3\xac\x94\xb3\xee\x53\x28\x4f\x4a\x22\x89\x3b\x3c\x24\xae",
+        b"\x3a\x62\xf0\xf0\xdb\xce\xf8\x23\xcf\xcc\x85\x48\x56\xea\x10\x28",
+        b"\x20\x44\x38\x17\x5e\xee\x7c\xe1\x36\xc9\x1b\x49\xa6\x79\x23\xff"
+    ], [
+        # very large key
+        8,
+        b"\x0d\xb3\xac\x94\xb3\xee\x53\x28\x4f\x4a\x22\x89\x3b\x3c\x24\xae",
+        b"\x3a\x62\xf0\xf0\xdb\xce\xf8\x23\xcf\xcc\x85\x48\x56\xea\x10\x28",
+        b"\x20\x54\xb9\xff\xf3\x4e\x37\x21\x44\x03\x34\x74\x68\x28\xe9\xed"
+        b"\x38\xde\x4b\x72\xe0\xa6\x9a\xdc\x17\x0a\x13\xb5\xe8\xd6\x46\x38"
+        b"\x5e\xa4\x03\x4a\xe6\xd2\x66\x00\xee\x23\x32\xc5\xed\x40\xad\x55"
+        b"\x7c\x86\xe3\x40\x3f\xbb\x30\xe4\xe1\xdc\x1a\xe0\x6b\x99\xa0\x71"
+        b"\x36\x8f\x51\x8d\x2c\x42\x66\x51\xc9\xe7\xe4\x37\xfd\x6c\x91\x5b"
+        b"\x1b\xbf\xc3\xa4\xce\xa7\x14\x91\x49\x0e\xa7\xaf\xb7\xdd\x02\x90"
+        b"\xa6\x78\xa4\xf4\x41\x12\x8d\xb1\x79\x2e\xab\x27\x76\xb2\x1e\xb4"
+        b"\x23\x8e\x07\x15\xad\xd4\x12\x7d\xff\x44\xe4\xb3\xe4\xcc\x4c\x4f"
+        b"\x99\x70\x08\x3f\x3f\x74\xbd\x69\x88\x73\xfd\xf6\x48\x84\x4f\x75"
+        b"\xc9\xbf\x7f\x9e\x0c\x4d\x9e\x5d\x89\xa7\x78\x39\x97\x49\x29\x66"
+        b"\x61\x67\x07\x61\x1c\xb9\x01\xde\x31\xa1\x97\x26\xb6\xe0\x8c\x3a"
+        b"\x80\x01\x66\x1f\x2d\x5c\x9d\xcc\x33\xb4\xaa\x07\x2f\x90\xdd\x0b"
+        b"\x3f\x54\x8d\x5e\xeb\xa4\x21\x13\x97\xe2\xfb\x06\x2e\x52\x6e\x1d"
+        b"\x68\xf4\x6a\x4c\xe2\x56\x18\x5b\x4b\xad\xc2\x68\x5f\xbe\x78\xe1"
+        b"\xc7\x65\x7b\x59\xf8\x3a\xb9\xab\x80\xcf\x93\x18\xd6\xad\xd1\xf5"
+        b"\x93\x3f\x12\xd6\xf3\x61\x82\xc8\xe8\x11\x5f\x68\x03\x0a\x12\x44"
+    ], [
+        # UTF-8 Greek characters "odysseus" / "telemachos"
+        8,
+        b"\xe1\xbd\x88\xce\xb4\xcf\x85\xcf\x83\xcf\x83\xce\xb5\xcf\x8d\xcf"
+        b"\x82",
+        b"\xce\xa4\xce\xb7\xce\xbb\xce\xad\xce\xbc\xce\xb1\xcf\x87\xce\xbf"
+        b"\xcf\x82",
+        b"\x43\x66\x6c\x9b\x09\xef\x33\xed\x8c\x27\xe8\xe8\xf3\xe2\xd8\xe6"
+    ]])
+def test_kdf(rounds, password, salt, expected):
+    derived = bcrypt.kdf(password, salt, len(expected), rounds)
+    assert derived == expected
+
+
+def test_kdf_str_password():
+    with pytest.raises(TypeError):
+        bcrypt.kdf(
+            six.text_type("password"), b"$2b$04$cVWp4XaNU8a4v1uMRum2SO", 10, 10
+        )
+
+
+def test_kdf_str_salt():
+    with pytest.raises(TypeError):
+        bcrypt.kdf(
+            b"password", six.text_type("salt"), 10, 10
+        )
+
+
+ at pytest.mark.parametrize(
+    ("password", "salt", "desired_key_bytes", "rounds", "error"),
+    [
+        (u"pass", b"$2b$04$cVWp4XaNU8a4v1uMRum2SO", 10, 10, TypeError),
+        (b"password", u"salt", 10, 10, TypeError),
+        (b"", b"$2b$04$cVWp4XaNU8a4v1uMRum2SO", 10, 10, ValueError),
+        (b"password", b"", 10, 10, ValueError),
+        (b"password", b"$2b$04$cVWp4XaNU8a4v1uMRum2SO", 0, 10, ValueError),
+        (b"password", b"$2b$04$cVWp4XaNU8a4v1uMRum2SO", -3, 10, ValueError),
+        (b"password", b"$2b$04$cVWp4XaNU8a4v1uMRum2SO", 513, 10, ValueError),
+        (b"password", b"$2b$04$cVWp4XaNU8a4v1uMRum2SO", 20, 0, ValueError),
+    ]
+)
+def test_invalid_params(password, salt, desired_key_bytes, rounds, error):
+    with pytest.raises(error):
+        bcrypt.kdf(password, salt, desired_key_bytes, rounds)
+
+
+def test_bcrypt_assert():
+    with pytest.raises(SystemError):
+        bcrypt._bcrypt_assert(False)
+
+
+def test_2a_wraparound_bug():
+    assert bcrypt.hashpw(
+        (b"0123456789" * 26)[:255], b"$2a$04$R1lJ2gkNaoPGdafE.H.16."
+    ) == b"$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi"
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..264d9aa
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,42 @@
+[tox]
+envlist = py26,py27,pypy,py33,py34,py35,pep8,py3pep8,packaging
+
+[testenv]
+# If you add a new dep here you probably need to add it in setup.py as well
+deps =
+    coverage
+    pytest
+commands =
+    coverage run -m pytest --strict {posargs}
+    coverage report -m --fail-under 100
+
+[testenv:pep8]
+deps =
+    flake8
+    flake8-import-order
+    pep8-naming
+commands =
+    flake8 .
+
... 22 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-bcrypt.git



More information about the Python-modules-commits mailing list