add support for additional bcrypt algo on autodetect, improve autodetect logic and log not matching hash length

This commit is contained in:
Peter Bieringer 2025-03-09 08:49:30 +01:00
parent 4f0e607583
commit cffb2aaae3

View file

@ -51,6 +51,7 @@ When bcrypt is installed:
import functools
import hmac
import os
import re
import threading
import time
from typing import Any, Tuple
@ -131,33 +132,49 @@ class Auth(auth.BaseAuth):
"""Check if ``hash_value`` and ``password`` match, plain method."""
return ("PLAIN", hmac.compare_digest(hash_value.encode(), password.encode()))
def _plain_fallback(self, method_orig, hash_value: str, password: str) -> tuple[str, bool]:
"""Check if ``hash_value`` and ``password`` match, plain method / fallback in case of hash length is not matching on autodetection."""
info = "PLAIN/fallback as hash length not matching for " + method_orig + ": " + str(len(hash_value))
return (info, hmac.compare_digest(hash_value.encode(), password.encode()))
def _bcrypt(self, bcrypt: Any, hash_value: str, password: str) -> tuple[str, bool]:
return ("BCRYPT", bcrypt.checkpw(password=password.encode('utf-8'), hashed_password=hash_value.encode()))
if self._encryption == "autodetect" and len(hash_value) != 60:
return self._plain_fallback("BCRYPT", hash_value, password)
else:
return ("BCRYPT", bcrypt.checkpw(password=password.encode('utf-8'), hashed_password=hash_value.encode()))
def _md5apr1(self, hash_value: str, password: str) -> tuple[str, bool]:
return ("MD5-APR1", apr_md5_crypt.verify(password, hash_value.strip()))
if self._encryption == "autodetect" and len(hash_value) != 37:
return self._plain_fallback("MD5-APR1", hash_value, password)
else:
return ("MD5-APR1", apr_md5_crypt.verify(password, hash_value.strip()))
def _sha256(self, hash_value: str, password: str) -> tuple[str, bool]:
return ("SHA-256", sha256_crypt.verify(password, hash_value.strip()))
if self._encryption == "autodetect" and len(hash_value) != 63:
return self._plain_fallback("SHA-256", hash_value, password)
else:
return ("SHA-256", sha256_crypt.verify(password, hash_value.strip()))
def _sha512(self, hash_value: str, password: str) -> tuple[str, bool]:
return ("SHA-512", sha512_crypt.verify(password, hash_value.strip()))
if self._encryption == "autodetect" and len(hash_value) != 106:
return self._plain_fallback("SHA-512", hash_value, password)
else:
return ("SHA-512", sha512_crypt.verify(password, hash_value.strip()))
def _autodetect(self, hash_value: str, password: str) -> tuple[str, bool]:
if hash_value.startswith("$apr1$", 0, 6) and len(hash_value) == 37:
if hash_value.startswith("$apr1$", 0, 6):
# MD5-APR1
return self._md5apr1(hash_value, password)
elif hash_value.startswith("$2y$", 0, 4) and len(hash_value) == 60:
elif re.match(r"^\$2(a|b|x|y)?\$", hash_value):
# BCRYPT
return self._verify_bcrypt(hash_value, password)
elif hash_value.startswith("$5$", 0, 3) and len(hash_value) == 63:
elif hash_value.startswith("$5$", 0, 3):
# SHA-256
return self._sha256(hash_value, password)
elif hash_value.startswith("$6$", 0, 3) and len(hash_value) == 106:
elif hash_value.startswith("$6$", 0, 3):
# SHA-512
return self._sha512(hash_value, password)
else:
# assumed plaintext
return self._plain(hash_value, password)
def _read_htpasswd(self, init: bool, suppress: bool) -> Tuple[bool, int, dict, int, int]: