Repository: privacyidea/privacyidea

Issue:
remove password hash calculation and switch to passlib completely
Currently there are several password hashes supported in the [`SQLIdResolver`](https://github.com/privacyidea/privacyidea/blob/b0cd514c17bea43337a01c33b72da136c27d52be/privacyidea/lib/resolvers/SQLIdResolver.py) which are more or less generated/verified with homegrown code.
We can remove most of the stuff and switch to [`passlib`](https://pypi.org/project/passlib/) (which is already used in some cases).
Some hashes (drupal, SSHA512 and SSHA256) are currently not available in passlib but can be added.



Discussion:

Resolving patch:
diff --git a/privacyidea/lib/resolvers/SQLIdResolver.py b/privacyidea/lib/resolvers/SQLIdResolver.py
--- a/privacyidea/lib/resolvers/SQLIdResolver.py
+++ b/privacyidea/lib/resolvers/SQLIdResolver.py
@@ -4,6 +4,8 @@
 #  License:  AGPLv3
 #  contact:  cornelius@privacyidea.org
 #
+#  2019-47-14 Paul Lettich <paul.lettich@netknights.it>
+#             Remove hash calculation and switch to passlib
 #  2016-07-15 Cornelius Kölbel <cornelius.koelbel@netknights.it>
 #             Add sha512 PHP hash as suggested by Rick Romero
 #  2016-04-08 Cornelius Kölbel <cornelius.koelbel@netknights.it>
@@ -33,22 +35,163 @@
 import binascii
 import re
 
-from .UserIdResolver import UserIdResolver
+from privacyidea.lib.resolvers.UserIdResolver import UserIdResolver
 
 from sqlalchemy import and_
 from sqlalchemy import create_engine
 from sqlalchemy.orm import sessionmaker, scoped_session
 
 import traceback
-from base64 import (b64decode,
-                    b64encode)
 import hashlib
-from privacyidea.lib.crypto import urandom, geturandom
 from privacyidea.lib.pooling import get_engine
 from privacyidea.lib.lifecycle import register_finalizer
-from privacyidea.lib.utils import (is_true, hash_password, PasswordHash,
-                                   check_sha, check_ssha, otrs_sha256, check_crypt, censor_connect_string, to_utf8)
-from passlib.hash import bcrypt
+from privacyidea.lib.utils import (is_true, censor_connect_string, to_utf8)
+from passlib.context import CryptContext
+from base64 import b64decode, b64encode
+from passlib.utils.binary import h64
+from passlib.utils.compat import uascii_to_str, u
+from passlib.utils.compat import unicode as pl_unicode
+from passlib.utils import to_unicode
+import passlib.utils.handlers as uh
+import passlib.exc as exc
+from passlib.registry import register_crypt_handler
+
+
+class phpass_drupal(uh.HasRounds, uh.HasSalt, uh.GenericHandler):  # pragma: no cover
+    """This class implements the PHPass Portable Hash (Drupal version), and follows the
+    :ref:`password-hash-api`.
+    """
+    name = "phpass_drupal"
+    setting_kwds = ("salt", "rounds")
+    checksum_chars = uh.HASH64_CHARS
+    checksum_size = 43
+
+    min_salt_size = max_salt_size = 8
+    salt_chars = uh.HASH64_CHARS
+
+    default_rounds = 19
+    min_rounds = 7
+    max_rounds = 30
+    rounds_cost = "log2"
+
+    ident = u'$S$'
+
+    @classmethod
+    def from_string(cls, hash):
+        hash = to_unicode(hash, "ascii", "hash")
+        ident, data = hash[0:3], hash[3:]
+        if ident != cls.ident:
+            raise exc.InvalidHashError()
+        rounds, salt, chk = data[0], data[1:9], data[9:]
+        return cls(
+            rounds=h64.decode_int6(rounds.encode("ascii")),
+            salt=salt,
+            checksum=chk or None,
+        )
+
+    def to_string(self):
+        hash = u"%s%s%s%s" % (self.ident,
+                              h64.encode_int6(self.rounds).decode("ascii"),
+                              self.salt,
+                              self.checksum or u'')
+        return uascii_to_str(hash)
+
+    def _calc_checksum(self, secret):
+        if isinstance(secret, pl_unicode):
+            secret = secret.encode("utf-8")
+        real_rounds = 1 << self.rounds
+        result = hashlib.sha512(self.salt.encode("ascii") + secret).digest()
+        r = 0
+        while r < real_rounds:
+            result = hashlib.sha512(result + secret).digest()
+            r += 1
+        return h64.encode_bytes(result).decode("ascii")[:self.checksum_size]
+
+
+class _SaltedBase64DigestHelper(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):  # pragma: no cover
+    """helper for ldap_salted_sha256/512"""
+    setting_kwds = ("salt", "salt_size")
+    checksum_chars = uh.PADDED_BASE64_CHARS
+
+    ident = None
+    _hash_func = None
+    _hash_regex = None
+    min_salt_size = 4
+    default_salt_size = 4
+    max_salt_size = 16
+
+    @classmethod
+    def from_string(cls, hash):
+        hash = to_unicode(hash, "ascii", "hash")
+        m = cls._hash_regex.match(hash)
+        if not m:
+            raise uh.exc.InvalidHashError(cls)
+        try:
+            data = b64decode(m.group("tmp").encode("ascii"))
+        except TypeError:
+            raise uh.exc.MalformedHashError(cls)
+        cs = cls.checksum_size
+        assert cs
+        return cls(checksum=data[:cs], salt=data[cs:])
+
+    def to_string(self):
+        data = self.checksum + self.salt
+        hash = self.ident + b64encode(data).decode("ascii")
+        return uascii_to_str(hash)
+
+    def _calc_checksum(self, secret):
+        if isinstance(secret, pl_unicode):
+            secret = secret.encode("utf-8")
+        return self._hash_func(secret + self.salt).digest()
+
+
+class ldap_salted_sha256(_SaltedBase64DigestHelper):
+    name = 'ldap_salted_sha256'
+    ident = u'{SSHA256}'
+    checksum_size = 32
+    max_salt_size = 32
+    _hash_func = hashlib.sha256
+    _hash_regex = re.compile(u(r"^\{SSHA256\}(?P<tmp>[+/a-zA-Z0-9]{48,}={0,2})$"))
+
+
+class ldap_salted_sha512(_SaltedBase64DigestHelper):
+    name = 'ldap_salted_sha512'
+    ident = u'{SSHA512}'
+    checksum_size = 64
+    max_salt_size = 64
+    _hash_func = hashlib.sha512
+    _hash_regex = re.compile(u(r"^\{SSHA512\}(?P<tmp>[+/a-zA-Z0-9]{88,}={0,2})$"))
+
+
+register_crypt_handler(phpass_drupal)
+register_crypt_handler(ldap_salted_sha256)
+register_crypt_handler(ldap_salted_sha512)
+
+# The list of supported password hash types for verification (passlib handler)
+pw_ctx = CryptContext(schemes=['phpass',
+                               'phpass_drupal',
+                               'ldap_salted_sha1',
+                               'ldap_salted_sha256',
+                               'ldap_salted_sha512',
+                               'ldap_sha1',
+                               'md5_crypt',
+                               'bcrypt',
+                               'sha512_crypt',
+                               'sha256_crypt',
+                               'hex_sha256',
+                               ])
+
+# List of supported password hash types for hash generation (name to passlib handler id)
+hash_type_dict = {"PHPASS": 'phpass',
+                  "SHA": 'ldap_sha1',
+                  "SSHA": 'ldap_salted_sha1',
+                  "SSHA256": 'ldap_salted_sha256',
+                  "SSHA512": 'ldap_salted_sha512',
+                  "OTRS": 'hex_sha256',
+                  "SHA256CRYPT": 'sha256_crypt',
+                  "SHA512CRYPT": 'sha512_crypt',
+                  "MD5CRYPT": 'md5_crypt',
+                  }
 
 log = logging.getLogger(__name__)
 ENCODING = "utf-8"
@@ -150,45 +293,33 @@ def _append_where_filter(conditions, table, where):
     def checkPass(self, uid, password):
         """
         This function checks the password for a given uid.
-        If ``password`` is a unicode object, it is converted to the database encoding first.
-        - returns true in case of success
-        -         false if password does not match
 
+        :param uid: uid of the user for which the password should be checked
+        :type uid: str
+        :param password: the password to check
+        :type password: str
+        :return: True if password matches the saved password hash, False otherwise
+        :rtype: bool
         """
 
         res = False
         userinfo = self.getUserInfo(uid)
-        if isinstance(password, unicode):
-            password = password.encode(self.encoding)
 
         database_pw = userinfo.get("password", "XXXXXXX")
-        if database_pw[:2] in ["$P", "$S"]:
-            # We have a phpass (wordpress) password
-            PH = PasswordHash()
-            res = PH.check_password(password, userinfo.get("password"))
-        # check salted hashed passwords
-#        elif database_pw[:2] == "$6":
-#            res = sha512_crypt.verify(password, userinfo.get("password"))
-        elif database_pw[:6].upper() == "{SSHA}":
-            res = check_ssha(database_pw, password, hashlib.sha1, 20)
-        elif database_pw[:9].upper() == "{SSHA256}":
-            res = check_ssha(database_pw, password, hashlib.sha256, 32)
-        elif database_pw[:9].upper() == "{SSHA512}":
-            res = check_ssha(database_pw, password, hashlib.sha512, 64)
-        # check for hashed password.
-        elif userinfo.get("password", "XXXXX")[:5].upper() == "{SHA}":
-            res = check_sha(database_pw, password)
-        elif len(userinfo.get("password")) == 64:
-            # OTRS sha256 password
-            res = otrs_sha256(database_pw, password)
-        elif database_pw[:3] in ["$1$", "$6$"]:
-            res = check_crypt(database_pw, password)
-        elif database_pw[:4] in ["$2a$", "$2b$", "$2y$"]:
-            # Do bcrypt hashing
-            res = bcrypt.verify(password, database_pw)
-        elif database_pw[:6] in ["1|$2a$", "1|$2b$", "1|$2y$"]:
-            # Do bcrypt hashing with some owncloud format
-            res = bcrypt.verify(password, database_pw[2:])
+
+        # remove owncloud hash format identifier (currently only version 1)
+        database_pw = re.sub(r'^1\|', '', database_pw)
+
+        # translate lower case hash identifier to uppercase
+        database_pw = re.sub(r'^{([a-z0-9]+)}',
+                             lambda match: '{{{}}}'.format(match.group(1).upper()),
+                             database_pw)
+
+        try:
+            res = pw_ctx.verify(password, database_pw)
+        except ValueError as _e:
+            # if the hash could not be identified / verified, just return False
+            pass
 
         return res
 
@@ -406,7 +537,8 @@ def loadConfig(self, config):
         return self
 
     def _create_engine(self):
-        log.info(u"using the connect string {0!s}".format(censor_connect_string(self.connect_string)))
+        log.info(u"using the connect string "
+                 u"{0!s}".format(censor_connect_string(self.connect_string)))
         try:
             log.debug("using pool_size={0!s}, pool_timeout={1!s}, pool_recycle={2!s}".format(
                 self.pool_size, self.pool_timeout, self.pool_recycle))
@@ -630,3 +762,24 @@ def editable(self):
         # Depending on the database this might look different
         # Usually this is "1"
         return is_true(self._editable)
+
+
+def hash_password(password, hashtype):
+    """
+    Hash a password with phppass, SHA, SSHA, SSHA256, SSHA512, OTRS
+
+    :param password: The password in plain text
+    :type password: str
+    :param hashtype: One of the hash types as string
+    :type hashtype: str
+    :return: The hashed password
+    :rtype: str
+    """
+    hashtype = hashtype.upper()
+    try:
+        password = pw_ctx.handler(hash_type_dict[hashtype]).hash(password)
+    except KeyError as _e:  # pragma: no cover
+        raise Exception("Unsupported password hashtype '{0!s}'. "
+                        "Use one of {1!s}.".format(hashtype, hash_type_dict.keys()))
+
+    return password
diff --git a/privacyidea/lib/utils.py b/privacyidea/lib/utils.py
--- a/privacyidea/lib/utils.py
+++ b/privacyidea/lib/utils.py
@@ -47,12 +47,8 @@
 from dateutil.tz import tzlocal, tzutc
 from netaddr import IPAddress, IPNetwork, AddrFormatError
 import hashlib
-import crypt
 import traceback
 import os
-import time
-from base64 import (b64decode, b64encode)
-
 
 try:
     import bcrypt
@@ -820,251 +816,6 @@ def parse_time_offset_from_now(s):
     return s, td
 
 
-def hash_password(password, hashtype):
-    """
-    Hash a password with phppass, SHA, SSHA, SSHA256, SSHA512, OTRS
-
-    :param password: The password in plain text 
-    :param hashtype: One of the hash types as string
-    :return: The hashed password
-    """
-    hashtype = hashtype.upper()
-    if hashtype == "PHPASS":
-        PH = PasswordHash()
-        password = PH.hash_password(password)
-    elif hashtype == "SHA":
-        password = hashlib.sha1(password).digest()
-        password = "{SHA}" + b64encode(password)
-    elif hashtype == "SSHA":
-        salt = geturandom(20)
-        hr = hashlib.sha1(password)
-        hr.update(salt)
-        pw = b64encode(hr.digest() + salt)
-        return "{SSHA}" + pw
-    elif hashtype == "SSHA256":
-        salt = geturandom(32)
-        hr = hashlib.sha256(password)
-        hr.update(salt)
-        pw = b64encode(hr.digest() + salt)
-        return "{SSHA256}" + pw
-    elif hashtype == "SSHA512":
-        salt = geturandom(64)
-        hr = hashlib.sha512(password)
-        hr.update(salt)
-        pw = b64encode(hr.digest() + salt)
-        return "{SSHA512}" + pw
-    elif hashtype == "OTRS":
-        password = hashlib.sha256(password).hexdigest()
-    elif hashtype == "MD5CRYPT":
-        salt = geturandom(8, True)
-        password = crypt.crypt(password, "$1$" + salt + "$")
-    elif hashtype == "SHA512CRYPT":
-        salt = geturandom(8, True)
-        password = crypt.crypt(password, "$6$" + salt + "$")
-    else:
-        raise Exception("Unsupported password hashtype. Use PHPASS, SHA, "
-                        "SSHA, SSHA256, SSHA512, OTRS.")
-    return password
-
-def check_crypt(pw_hash, password):
-    return crypt.crypt(password, pw_hash) == pw_hash
-
-def check_ssha(pw_hash, password, hashfunc, length):
-    pw_hash_bin = b64decode(pw_hash.split("}")[1])
-    digest = pw_hash_bin[:length]
-    salt = pw_hash_bin[length:]
-    hr = hashfunc(password)
-    hr.update(salt)
-    return digest == hr.digest()
-
-
-def check_sha(pw_hash, password):
-    b64_db_password = pw_hash[5:]
-    hr = hashlib.sha1(password).digest()
-    b64_password = b64encode(hr)
-    return b64_password == b64_db_password
-
-
-def otrs_sha256(pw_hash, password):
-    hr = hashlib.sha256(password)
-    digest = binascii.hexlify(hr.digest())
-    return pw_hash == digest
-
-
-class PasswordHash(object):
-    def __init__(self, iteration_count_log2=8, portable_hashes=True,
-                 algorithm=''):
-        alg = algorithm.lower()
-        if alg in ['blowfish', 'bcrypt'] and _bcrypt_hashpw is None:
-            raise NotImplementedError('The bcrypt module is required')
-        self.itoa64 = \
-            './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
-        if not (4 <= iteration_count_log2 <= 31):
-            iteration_count_log2 = 8
-        self.iteration_count_log2 = iteration_count_log2
-        self.portable_hashes = portable_hashes
-        self.algorithm = algorithm
-        self.random_state = '{0!r}{1!r}'.format(time.time(), _pid)
-
-    def get_random_bytes(self, count):
-        outp = ''
-        try:
-            outp = geturandom(count)
-        except Exception as exx:  # pragma: no cover
-            log.debug("problem getting urandom: {0!s}".format(exx))
-        if len(outp) < count:  # pragma: no cover
-            outp = ''
-            rem = count
-            while rem > 0:
-                self.random_state = hashlib.md5(str(time.time())
-                                                + self.random_state).hexdigest()
-                outp += hashlib.md5(self.random_state).digest()
-                rem -= 1
-            outp = outp[:count]
-        return outp
-
-    def encode64(self, inp, count):
-        outp = ''
-        cur = 0
-        while cur < count:
-            value = ord(inp[cur])
-            cur += 1
-            outp += self.itoa64[value & 0x3f]
-            if cur < count:
-                value |= (ord(inp[cur]) << 8)
-            outp += self.itoa64[(value >> 6) & 0x3f]
-            if cur >= count:
-                break
-            cur += 1
-            if cur < count:
-                value |= (ord(inp[cur]) << 16)
-            outp += self.itoa64[(value >> 12) & 0x3f]
-            if cur >= count:
-                break
-            cur += 1
-            outp += self.itoa64[(value >> 18) & 0x3f]
-        return outp
-
-    def gensalt_private(self, inp):  # pragma: no cover
-        outp = '$P$'
-        outp += self.itoa64[min([self.iteration_count_log2 + 5, 30])]
-        outp += self.encode64(inp, 6)
-        return outp
-
-    def crypt_private(self, pw, setting):  # pragma: no cover
-        outp = '*0'
-        if setting.startswith(outp):
-            outp = '*1'
-        if setting[0:3] not in ['$P$', '$H$', '$S$']:
-            return outp
-        count_log2 = self.itoa64.find(setting[3])
-        if not (7 <= count_log2 <= 30):
-            return outp
-        count = 1 << count_log2
-        salt = setting[4:12]
-        if len(salt) != 8:
-            return outp
-        if not isinstance(pw, str):
-            pw = pw.encode('utf-8')
-
-        hash_func = hashlib.md5
-        encoding_len = 16
-        if setting.startswith('$S$'):
-            hash_func = hashlib.sha512
-            encoding_len = 33
-
-        hx = hash_func(salt + pw).digest()
-        while count:
-            hx = hash_func(hx + pw).digest()
-            count -= 1
-        hashed_pw = self.encode64(hx, encoding_len)
-
-        if setting.startswith('$S$'):
-            hashed_pw = hashed_pw[:-1]
-        return setting[:12] + hashed_pw
-
-    def gensalt_extended(self, inp):  # pragma: no cover
-        count_log2 = min([self.iteration_count_log2 + 8, 24])
-        count = (1 << count_log2) - 1
-        outp = '_'
-        outp += self.itoa64[count & 0x3f]
-        outp += self.itoa64[(count >> 6) & 0x3f]
-        outp += self.itoa64[(count >> 12) & 0x3f]
-        outp += self.itoa64[(count >> 18) & 0x3f]
-        outp += self.encode64(inp, 3)
-        return outp
-
-    def gensalt_blowfish(self, inp):  # pragma: no cover
-        itoa64 = \
-            './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
-        outp = '$2a$'
-        outp += chr(ord('0') + self.iteration_count_log2 // 10)
-        outp += chr(ord('0') + self.iteration_count_log2 % 10)
-        outp += '$'
-        cur = 0
-        while True:
-            c1 = ord(inp[cur])
-            cur += 1
-            outp += itoa64[c1 >> 2]
-            c1 = (c1 & 0x03) << 4
-            if cur >= 16:
-                outp += itoa64[c1]
-                break
-            c2 = ord(inp[cur])
-            cur += 1
-            c1 |= c2 >> 4
-            outp += itoa64[c1]
-            c1 = (c2 & 0x0f) << 2
-            c2 = ord(inp[cur])
-            cur += 1
-            c1 |= c2 >> 6
-            outp += itoa64[c1]
-            outp += itoa64[c2 & 0x3f]
-        return outp
-
-    def hash_password(self, pw):  # pragma: no cover
-        rnd = ''
-        alg = self.algorithm.lower()
-        if (not alg or alg in ['blowfish', 'bcrypt'] and not
-        self.portable_hashes):
-            if _bcrypt_hashpw is None and alg in ['blowfish', 'bcrypt']:
-                raise NotImplementedError('The bcrypt module is required')
-            else:
-                rnd = self.get_random_bytes(16)
-                salt = self.gensalt_blowfish(rnd)
-                hx = _bcrypt_hashpw(pw, salt)
-                if len(hx) == 60:
-                    return hx
-        if (not alg or alg == 'ext-des') and not self.portable_hashes:
-            if len(rnd) < 3:
-                rnd = self.get_random_bytes(3)
-            hx = crypt.crypt(pw, self.gensalt_extended(rnd))
-            if len(hx) == 20:
-                return hx
-        if len(rnd) < 6:
-            rnd = self.get_random_bytes(6)
-        hx = self.crypt_private(pw, self.gensalt_private(rnd))
-        if len(hx) == 34:
-            return hx
-        return '*'
-
-    def check_password(self, pw, stored_hash):
-        # This part is different with the original PHP
-        if stored_hash.startswith('$2a$'):
-            # bcrypt
-            if _bcrypt_hashpw is None:  # pragma: no cover
-                raise NotImplementedError('The bcrypt module is required')
-            hx = _bcrypt_hashpw(pw, stored_hash)
-        elif stored_hash.startswith('_'):
-            # ext-des
-            stored_hash = stored_hash[1:]
-            hx = crypt.crypt(pw, stored_hash)
-        else:
-            # portable hash
-            hx = self.crypt_private(pw, stored_hash)
-        return stored_hash == hx
-
-
 def parse_int(s, default=0):
     """
     Returns an integer either to base10 or base16.


Test patch:
diff --git a/tests/test_lib_resolver.py b/tests/test_lib_resolver.py
--- a/tests/test_lib_resolver.py
+++ b/tests/test_lib_resolver.py
@@ -17,10 +17,10 @@
 import responses
 import datetime
 import uuid
+import pytest
 from privacyidea.lib.resolvers.LDAPIdResolver import IdResolver as LDAPResolver
 from privacyidea.lib.resolvers.SQLIdResolver import IdResolver as SQLResolver
 from privacyidea.lib.resolvers.SCIMIdResolver import IdResolver as SCIMResolver
-from privacyidea.lib.resolvers.SQLIdResolver import PasswordHash
 from privacyidea.lib.resolvers.UserIdResolver import UserIdResolver
 from privacyidea.lib.resolvers.LDAPIdResolver import (SERVERPOOL_ROUNDS, SERVERPOOL_SKIP)
 
@@ -144,7 +144,7 @@ class SQLResolverTestCase(MyTestCase):
     """
     Test the SQL Resolver
     """
-    num_users = 11
+    num_users = 13
     parameters = {'Driver': 'sqlite',
                   'Server': '/tests/testdata/',
                   'Database': "testuser.sqlite",
@@ -239,43 +239,39 @@ def test_02_check_passwords(self):
         # SHA256 of "dunno"
         # 772cb52221f19104310cd2f549f5131fbfd34e0f4de7590c87b1d73175812607
 
-        result = y.checkPass(3, "dunno")
-        assert result is True
+        self.assertTrue(y.checkPass('3', "dunno"))
         '''
         SHA1 base64 encoded of "dunno"
         Lg8DuLoXOwvPkMABDprnaTp0JOA=
         '''
-        result = y.checkPass(2, "dunno")
-        assert result is True
+        self.assertTrue(y.checkPass('2', "dunno"))
 
-        result = y.checkPass(1, "dunno")
-        assert result is True
+        self.assertTrue(y.checkPass('1', "dunno"))
 
-        result = y.checkPass(4, "dunno")
-        assert result is True
+        self.assertTrue(y.checkPass('4', "dunno"))
 
-        result = y.checkPass(5, "dunno")
-        assert result is True
+        self.assertTrue(y.checkPass('5', "dunno"))
 
         '''
         >>> PH = PasswordHash()
         >>> PH.hash_password("testpassword")
         '$P$Bz4R6lzp6VWCL0SCeTozqKHNV8DM.Q/'
         '''
-        result = y.checkPass(6, "testpassword")
-        self.assertTrue(result)
-        
-        result = y.checkPass(8, "dunno")
-        self.assertTrue(result)
-        
-        result = y.checkPass(9, "dunno")
-        self.assertTrue(result)
+        self.assertTrue(y.checkPass('6', "testpassword"))
+
+        self.assertTrue(y.checkPass('8', "dunno"))
+
+        self.assertTrue(y.checkPass('9', "dunno"))
 
         # bcrypt hashes
-        self.assertTrue(y.checkPass(10, "test"))
-        self.assertFalse(y.checkPass(10, "testw"))
-        self.assertTrue(y.checkPass(11, "test"))
-        self.assertFalse(y.checkPass(11, "testw"))
+        self.assertTrue(y.checkPass('10', "test"))
+        self.assertFalse(y.checkPass('10', "testw"))
+        self.assertTrue(y.checkPass('11', "test"))
+        self.assertFalse(y.checkPass('11', "testw"))
+        self.assertTrue(y.checkPass('12', "dunno"))
+        self.assertFalse(y.checkPass('12', "dunno2"))
+        # unknown password hash type
+        self.assertFalse(y.checkPass('13', "dunno2"))
 
     def test_03_testconnection(self):
         y = SQLResolver()
@@ -291,7 +287,7 @@ def test_05_add_user_update_delete(self):
                           "email": "achmed@world.net",
                           "password": "passw0rd",
                           "mobile": "12345"})
-        self.assertTrue(uid > 8)
+        self.assertTrue(uid > self.num_users)
         self.assertTrue(y.checkPass(uid, "passw0rd"))
         self.assertFalse(y.checkPass(uid, "password"))
         # check that we actually store SSHA256
@@ -299,7 +295,7 @@ def test_05_add_user_update_delete(self):
         self.assertTrue(stored_password.startswith("{SSHA256}"), stored_password)
 
         uid = y.getUserId("achmed")
-        self.assertTrue(uid > 8)
+        self.assertTrue(uid > self.num_users)
 
         r = y.update_user(uid, {"username": "achmed2",
                                 "password": "test"})
@@ -356,7 +352,7 @@ def test_06_append_where_filter(self):
         userlist = y.getUserList()
         self.assertTrue(len(userlist) == 0, userlist)
 
-    def test_07_add_user_update_delete_ssha512(self):
+    def test_07_add_user_update_delete_hashes(self):
         y = SQLResolver()
         parameters = self.parameters.copy()
         # sha256 at first
@@ -366,34 +362,105 @@ def test_07_add_user_update_delete_ssha512(self):
                           "email": "achmed@world.net",
                           "password": "passw0rd",
                           "mobile": "12345"})
-        self.assertTrue(uid > 8)
+        self.assertTrue(uid > self.num_users)
         self.assertTrue(y.checkPass(uid, "passw0rd"))
         self.assertFalse(y.checkPass(uid, "password"))
         # check that we actually store SSHA256 at first
         stored_password = y.TABLE.filter_by(username="achmed").first().password
         self.assertTrue(stored_password.startswith("{SSHA256}"), stored_password)
 
-        r = y.update_user(uid, {"username": "achmed2",
-                                "password": "test"})
+        self.assertTrue(y.update_user(uid, {"username": "achmed2",
+                                            "password": "test"}))
         stored_password = y.TABLE.filter_by(username="achmed2").first().password
         self.assertTrue(stored_password.startswith("{SSHA256}"), stored_password)
-        uname = y.getUsername(uid)
-        self.assertEqual(uname, "achmed2")
-        r = y.checkPass(uid, "test")
-        self.assertTrue(r)
+        self.assertEqual(y.getUsername(uid), "achmed2")
+        self.assertTrue(y.checkPass(uid, "test"))
 
-        # change to ssha512
+        # change to SSHA512
         y = SQLResolver()
         parameters["Password_Hash_Type"] = "SSHA512"
         y.loadConfig(parameters)
 
-        r = y.update_user(uid, {"username": "achmed2",
-                                "password": "test2"})
+        self.assertTrue(y.update_user(uid, {"username": "achmed2",
+                                            "password": "test2"}))
         stored_password = y.TABLE.filter_by(username="achmed2").first().password
         self.assertTrue(stored_password.startswith("{SSHA512}"), stored_password)
         self.assertTrue(y.checkPass(uid, "test2"))
         self.assertFalse(y.checkPass(uid, "test"))
 
+        # PHPASS
+        parameters["Password_Hash_Type"] = "PHPASS"
+        y.loadConfig(parameters)
+        self.assertTrue(y.update_user(uid, {"username": "achmed2",
+                                            "password": "test3"}))
+        stored_password = y.TABLE.filter_by(username="achmed2").first().password
+        self.assertTrue(stored_password.startswith("$P$"), stored_password)
+        self.assertTrue(y.checkPass(uid, "test3"))
+        self.assertFalse(y.checkPass(uid, "test"))
+
+        # SHA
+        parameters["Password_Hash_Type"] = "SHA"
+        y.loadConfig(parameters)
+        self.assertTrue(y.update_user(uid, {"username": "achmed2",
+                                            "password": "test4"}))
+        stored_password = y.TABLE.filter_by(username="achmed2").first().password
+        self.assertTrue(stored_password.startswith("{SHA}"), stored_password)
+        self.assertTrue(y.checkPass(uid, "test4"))
+        self.assertFalse(y.checkPass(uid, "test"))
+
+        # SSHA
+        parameters["Password_Hash_Type"] = "SSHA"
+        y.loadConfig(parameters)
+        self.assertTrue(y.update_user(uid, {"username": "achmed2",
+                                            "password": "test5"}))
+        stored_password = y.TABLE.filter_by(username="achmed2").first().password
+        self.assertTrue(stored_password.startswith("{SSHA}"), stored_password)
+        self.assertTrue(y.checkPass(uid, "test5"))
+        self.assertFalse(y.checkPass(uid, "test"))
+
+        # SHA256CRYPT
+        parameters["Password_Hash_Type"] = "SHA256CRYPT"
+        y.loadConfig(parameters)
+        self.assertTrue(y.update_user(uid, {"username": "achmed2",
+                                            "password": "test6"}))
+        stored_password = y.TABLE.filter_by(username="achmed2").first().password
+        self.assertTrue(stored_password.startswith("$5$rounds="), stored_password)
+        self.assertTrue(y.checkPass(uid, "test6"))
+        self.assertFalse(y.checkPass(uid, "test"))
+
+        # SHA512CRYPT
+        parameters["Password_Hash_Type"] = "SHA512CRYPT"
+        y.loadConfig(parameters)
+        self.assertTrue(y.update_user(uid, {"username": "achmed2",
+                                            "password": "test7"}))
+        stored_password = y.TABLE.filter_by(username="achmed2").first().password
+        self.assertTrue(stored_password.startswith("$6$rounds="), stored_password)
+        self.assertTrue(y.checkPass(uid, "test7"))
+        self.assertFalse(y.checkPass(uid, "test"))
+
+        # MD5CRYPT
+        parameters["Password_Hash_Type"] = "MD5CRYPT"
+        y.loadConfig(parameters)
+        self.assertTrue(y.update_user(uid, {"username": "achmed2",
+                                            "password": "test8"}))
+        stored_password = y.TABLE.filter_by(username="achmed2").first().password
+        self.assertTrue(stored_password.startswith("$1$"), stored_password)
+        self.assertTrue(y.checkPass(uid, "test8"))
+        self.assertFalse(y.checkPass(uid, "test"))
+
+        # TODO: check unknown hash type
+        parameters["Password_Hash_Type"] = "UNKNOWN"
+        y.loadConfig(parameters)
+        with pytest.raises(Exception) as e:
+            self.assertTrue(y.update_user(uid, {"username": "achmed2",
+                                                "password": "test9"}))
+        self.assertTrue(e.value.args[0].startswith("Unsupported password hashtype 'UNKNOWN'."),
+                        e.value)
+
+        # set hash type to default
+        parameters.pop("Password_Hash_Type")
+        y.loadConfig(parameters)
+
         # Now we delete the user
         y.delete_user(uid)
         # Now there should be no achmed anymore
@@ -403,15 +470,15 @@ def test_07_add_user_update_delete_ssha512(self):
         self.assertFalse(uid)
 
         # Add a new user
-        uid = y.add_user({"username":"hans",
+        uid = y.add_user({"username": "hans",
                           "email": "hans@world.net",
                           "password": u"foo",
                           "mobile": "12345"})
         self.assertTrue(y.checkPass(uid, u"foo"))
         self.assertFalse(y.checkPass(uid, u"bar"))
-        # check that we actually store SSHA512 now
+        # check that we actually store SSHA265 now since it is the default
         stored_password = y.TABLE.filter_by(username="hans").first().password
-        self.assertTrue(stored_password.startswith("{SSHA512}"), stored_password)
+        self.assertTrue(stored_password.startswith("{SSHA256}"), stored_password)
 
         y.delete_user(uid)
 
@@ -2302,39 +2369,3 @@ def test_15_try_to_delete_used_resolver(self):
         self.assertRaises(Exception, delete_resolver, self.resolvername1)
         delete_realm("myrealm")
         delete_resolver(self.resolvername1)
-
-
-class PasswordHashTestCase(MyTestCase):
-    """
-    Test the password hashing in the SQL database
-    """
-    def test_01_get_random_bytes(self):
-        ph = PasswordHash()
-        rb = ph.get_random_bytes(10)
-        self.assertTrue(len(rb) == 10, len(rb))
-        rb = ph.get_random_bytes(100)
-        self.assertTrue(len(rb) == 100, len(rb))
-        _ph = PasswordHash(iteration_count_log2=32)
-
-    def test_02_checkpassword_crypt(self):
-        ph = PasswordHash()
-        r = ph.check_password("Hallo", "_xyFAfsLH.5Z.Q")
-        self.assertTrue(r)
-
-        # Drupal passwords
-        r = ph.check_password("mohsen123",
-                              "$S$D98Bg3ANTUrjVwx073djifdH1KxbyzXQaPrmbpxGOu4VXFyMClRz")
-        self.assertTrue(r)
-
-        r = ph.check_password("DevYubic",
-                              "$S$D3f83Mbqy.9SV8Ip1zo7nRauu/4HVFOXfEkfsq.8ryCdFV40DCLl")
-        self.assertTrue(r)
-
-        # blowfish crypt
-        # user http://www.passwordtool.hu/
-        r = ph.check_password("asdasdasd", "$2a$07$4MnpSZo6xAIT7PArFIcO7uc/dfkP60Nuq2KmIQH3rdjrcG9/Ef48.")
-        self.assertTrue(r)
-
-        # Wordpress password hash
-        r = ph.check_password("asdasdasd", "$P$BkFOEwjLEEQVVJqRp3wANZbH83ZnN6.")
-        self.assertTrue(r)
diff --git a/tests/test_lib_utils.py b/tests/test_lib_utils.py
--- a/tests/test_lib_utils.py
+++ b/tests/test_lib_utils.py
@@ -12,9 +12,7 @@
                                    int_to_hex, compare_value_value,
                                    parse_time_offset_from_now,
                                    parse_timedelta, to_unicode,
-                                   hash_password, PasswordHash, check_ssha,
-                                   check_sha, otrs_sha256, parse_int, check_crypt,
-                                   convert_column_to_unicode, censor_connect_string,
+                                   parse_int, convert_column_to_unicode, censor_connect_string,
                                    truncate_comma_list, check_pin_policy,
                                    get_module_class, decode_base32check)
 from datetime import timedelta, datetime
@@ -369,47 +367,6 @@ def test_14_to_unicode(self):
         su = to_unicode(s)
         self.assertEqual(su, u"kölbel")
 
-    def test_15_hash_passwords(self):
-        p_hash = hash_password("pass0rd", "phpass")
-        PH = PasswordHash()
-        self.assertTrue(PH.check_password("pass0rd", p_hash))
-        self.assertFalse(PH.check_password("passord", p_hash))
-
-        # {SHA}
-        p_hash = hash_password("passw0rd", "sha")
-        self.assertTrue(check_sha(p_hash, "passw0rd"))
-        self.assertFalse(check_sha(p_hash, "password"))
-
-        # OTRS
-        p_hash = hash_password("passw0rd", "otrs")
-        self.assertTrue(otrs_sha256(p_hash, "passw0rd"))
-        self.assertFalse(otrs_sha256(p_hash, "password"))
-
-        # {SSHA}
-        p_hash = hash_password("passw0rd", "ssha")
-        self.assertTrue(check_ssha(p_hash, "passw0rd", hashlib.sha1, 20))
-        self.assertFalse(check_ssha(p_hash, "password", hashlib.sha1, 20))
-
-        # {SSHA256}
-        p_hash = hash_password("passw0rd", "ssha256")
-        self.assertTrue(check_ssha(p_hash, "passw0rd", hashlib.sha256, 32))
-        self.assertFalse(check_ssha(p_hash, "password", hashlib.sha256, 32))
-
-        # {SSHA512}
-        p_hash = hash_password("passw0rd", "ssha512")
-        self.assertTrue(check_ssha(p_hash, "passw0rd", hashlib.sha512, 64))
-        self.assertFalse(check_ssha(p_hash, "password", hashlib.sha512, 64))
-        
-        # MD5Crypt
-        p_hash = hash_password("passw0rd", "md5crypt")
-        self.assertTrue(check_crypt(p_hash, "passw0rd"))
-        self.assertFalse(check_crypt(p_hash, "password"))
-        
-        # SHA512Crypt
-        p_hash = hash_password("passw0rd", "sha512crypt")
-        self.assertTrue(check_crypt(p_hash, "passw0rd"))
-        self.assertFalse(check_crypt(p_hash, "password"))
-
     def test_16_parse_int(self):
         r = parse_int("xxx", 12)
         self.assertEqual(r, 12)
diff --git a/tests/testdata/testuser.sqlite b/tests/testdata/testuser.sqlite
Binary files a/tests/testdata/testuser.sqlite and b/tests/testdata/testuser.sqlite differ