[GH-ISSUE #3850] passlib is unmaintained #1923

Closed
opened 2026-02-27 11:19:55 +03:00 by kerem · 2 comments
Owner

Originally created by @ntninja on GitHub (Dec 27, 2025).
Original GitHub issue: https://github.com/modoboa/modoboa/issues/3850

Wanted to let you know your passlib dependency is unmaintained:
https://foss.heptapod.net/python-libs/passlib/-/issues/187
https://github.com/ansible/ansible/issues/81949#issuecomment-1768772765

I’ve got a patch for bcrypt 5.0.0 and non-broken tests at:
https://foss.heptapod.net/python-libs/passlib/-/work_items/197

Maybe Modoboa wants to do a minimal-effort fork of it?

Originally created by @ntninja on GitHub (Dec 27, 2025). Original GitHub issue: https://github.com/modoboa/modoboa/issues/3850 Wanted to let you know your `passlib` dependency is unmaintained: https://foss.heptapod.net/python-libs/passlib/-/issues/187 https://github.com/ansible/ansible/issues/81949#issuecomment-1768772765 I’ve got a patch for `bcrypt` 5.0.0 and non-broken tests at: https://foss.heptapod.net/python-libs/passlib/-/work_items/197 Maybe Modoboa wants to do a minimal-effort fork of it?
kerem closed this issue 2026-02-27 11:19:55 +03:00
Author
Owner

@ntninja commented on GitHub (Dec 27, 2025):

Alternative quickfix is a patch like the following to bypass passlib entirely to keep bcrypt at least working:

diff --git a/modoboa/core/password_hashers/advanced.py b/modoboa/core/password_hashers/advanced.py
index 12366b9b1..2ced08ea6 100644
--- a/modoboa/core/password_hashers/advanced.py
+++ b/modoboa/core/password_hashers/advanced.py
@@ -5,6 +5,7 @@ This module relies on `passlib` to provide more secure hashers.
 """
 
 from passlib.hash import bcrypt, md5_crypt, sha256_crypt, sha512_crypt, ldap_salted_sha1
+import bcrypt as bcrypt_real
 
 try:
     from argon2 import PasswordHasher as argon2_hasher
@@ -39,11 +40,17 @@ class BLFCRYPTHasher(PasswordHasher):
         # when using the bcrypt hasher.
         # rounds = parameters.get_global_parameter("rounds_number")
         # To get around this, I use the default of 12.
+        if isinstance(clearvalue, str):
+            clearvalue = clearvalue.encode("utf-8")
         rounds = 12
-        return bcrypt.using(rounds=rounds).hash(clearvalue)
+        return bcrypt_real.hashpw(clearvalue[:72], bcrypt_real.gensalt(rounds))
 
     def verify(self, clearvalue, hashed_value):
-        return bcrypt.verify(clearvalue, hashed_value)
+        if isinstance(clearvalue, str):
+            clearvalue = clearvalue.encode("utf-8")
+        if isinstance(hashed_value, str):
+            hashed_value = hashed_value.encode("utf-8")
+        return bcrypt_real.checkpw(clearvalue[:72], hashed_value)
 
 
 class MD5CRYPTHasher(PasswordHasher):
diff --git a/pyproject.toml b/pyproject.toml
index ba6f8a667..aff0fd46f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -43,7 +43,7 @@ dependencies = [
   "django-oauth-toolkit",
   "django-cors-headers",
   "passlib~=1.7.4",
-  "bcrypt==4.3.0", # Requires libffi-dev and python-dev
+  "bcrypt~=5.0", # Requires libffi-dev and python-dev
   "asgiref",
   "dnspython==2.8.0",
   "feedparser==6.0.12",
<!-- gh-comment-id:3694269202 --> @ntninja commented on GitHub (Dec 27, 2025): Alternative quickfix is a patch like the following to bypass `passlib` entirely to keep `bcrypt` at least working: ```diff diff --git a/modoboa/core/password_hashers/advanced.py b/modoboa/core/password_hashers/advanced.py index 12366b9b1..2ced08ea6 100644 --- a/modoboa/core/password_hashers/advanced.py +++ b/modoboa/core/password_hashers/advanced.py @@ -5,6 +5,7 @@ This module relies on `passlib` to provide more secure hashers. """ from passlib.hash import bcrypt, md5_crypt, sha256_crypt, sha512_crypt, ldap_salted_sha1 +import bcrypt as bcrypt_real try: from argon2 import PasswordHasher as argon2_hasher @@ -39,11 +40,17 @@ class BLFCRYPTHasher(PasswordHasher): # when using the bcrypt hasher. # rounds = parameters.get_global_parameter("rounds_number") # To get around this, I use the default of 12. + if isinstance(clearvalue, str): + clearvalue = clearvalue.encode("utf-8") rounds = 12 - return bcrypt.using(rounds=rounds).hash(clearvalue) + return bcrypt_real.hashpw(clearvalue[:72], bcrypt_real.gensalt(rounds)) def verify(self, clearvalue, hashed_value): - return bcrypt.verify(clearvalue, hashed_value) + if isinstance(clearvalue, str): + clearvalue = clearvalue.encode("utf-8") + if isinstance(hashed_value, str): + hashed_value = hashed_value.encode("utf-8") + return bcrypt_real.checkpw(clearvalue[:72], hashed_value) class MD5CRYPTHasher(PasswordHasher): diff --git a/pyproject.toml b/pyproject.toml index ba6f8a667..aff0fd46f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ dependencies = [ "django-oauth-toolkit", "django-cors-headers", "passlib~=1.7.4", - "bcrypt==4.3.0", # Requires libffi-dev and python-dev + "bcrypt~=5.0", # Requires libffi-dev and python-dev "asgiref", "dnspython==2.8.0", "feedparser==6.0.12", ```
Author
Owner

@tonioo commented on GitHub (Jan 6, 2026):

@ntninja This one was on my list for quite some time... I've finally decided to replace it with libpass, which looks maintained.
Thanks for the report anyway

<!-- gh-comment-id:3713605592 --> @tonioo commented on GitHub (Jan 6, 2026): @ntninja This one was on my list for quite some time... I've finally decided to replace it with libpass, which looks maintained. Thanks for the report anyway
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/modoboa-modoboa#1923
No description provided.