text: """
  Hasher functions derived from django's password hashers.py 
  Credit: https://github.com/django/django
  """
  import binascii
  import hashlib
  import importlib
  import warnings
  from collections import OrderedDict
  
  from kool.utils.crypto import constant_time_compare, get_random_string
  from kool.utils.encoding import force_bytes, force_text
  
  
  UNUSABLE_PASSWORD_PREFIX = '!'  # This will never be a valid encoded hash
  UNUSABLE_PASSWORD_SUFFIX_LENGTH = 40  # number of random chars to add after UNUSABLE_PASSWORD_PREFIX
  
  
  def get_hasher():
      """Returns default hasher. To be extended to support multiple hashers."""
      return BCryptSHA256PasswordHasher()
  
  
  def check_password(password, encoded, setter=None):
      """
      Return a boolean of whether the raw password matches the three
      part encoded digest.
  
      If setter is specified, it'll be called when you need to
      regenerate the password.
      """
      hasher = get_hasher()
  
      is_correct = hasher.verify(password, encoded)
  
      if setter and is_correct:
          setter(password)
      return is_correct
  
  
  def make_password(password, salt=None, hasher='default'):
      """
      Turn a plain-text password into a hash for storage
  
      Same as encode() but generate a new random salt. If password is None then
      return a concatenation of UNUSABLE_PASSWORD_PREFIX and a random string,
      which disallows logins. Additional random string reduces chances of gaining
      access to staff or superuser accounts. 
      """
      if password is None:
          return UNUSABLE_PASSWORD_PREFIX + get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH)
      
      hasher = get_hasher()
  
      if not salt:
          salt = hasher.salt()
  
      return hasher.encode(password, salt)
  
  
  def mask_hash(hash, show=6, char="*"):
      """
      Return the given hash, with only the first ``show`` number shown. The
      rest are masked with ``char`` for security reasons.
      """
      masked = hash[:show]
      masked += char * len(hash[show:])
      return masked
      
  
  class BasePasswordHasher:
      """
      Abstract base class for password hashers
  
      When creating your own hasher, you need to override algorithm,
      verify(), encode() and safe_summary().
  
      PasswordHasher objects are immutable.
      """
      algorithm = None
      library = None
  
      def _load_library(self):
          if self.library is not None:
              if isinstance(self.library, (tuple, list)):
                  name, mod_path = self.library
              else:
                  mod_path = self.library
              try:
                  module = importlib.import_module(mod_path)
              except ImportError as e:
                  raise ValueError("Couldn't load %r algorithm library: %s" %
                                   (self.__class__.__name__, e))
              return module
          raise ValueError("Hasher %r doesn't specify a library attribute" %
                           self.__class__.__name__)
  
      def salt(self):
          """Generate a cryptographically secure nonce salt in ASCII."""
          return get_random_string()
  
      def verify(self, password, encoded):
          """Check if the given password is correct."""
          raise NotImplementedError('subclasses of BasePasswordHasher must provide a verify() method')
  
      def encode(self, password, salt):
          """
          Create an encoded database value.
  
          The result is normally formatted as "algorithm$salt$hash" and
          must be fewer than 128 characters.
          """
          raise NotImplementedError('subclasses of BasePasswordHasher must provide an encode() method')
  
      def safe_summary(self, encoded):
          """
          Return a summary of safe values.
  
          The result is a dictionary and will be used where the password field
          must be displayed to construct a safe representation of the password.
          """
          raise NotImplementedError('subclasses of BasePasswordHasher must provide a safe_summary() method')
  
      def must_update(self, encoded):
          return False
  
      def harden_runtime(self, password, encoded):
          """
          Bridge the runtime gap between the work factor supplied in `encoded`
          and the work factor suggested by this hasher.
  
          Taking PBKDF2 as an example, if `encoded` contains 20000 iterations and
          `self.iterations` is 30000, this method should run password through
          another 10000 iterations of PBKDF2. Similar approaches should exist
          for any hasher that has a work factor. If not, this method should be
          defined as a no-op to silence the warning.
          """
          warnings.warn('subclasses of BasePasswordHasher should provide a harden_runtime() method')
  
  
  class BCryptSHA256PasswordHasher(BasePasswordHasher):
      """
      Secure password hashing using the bcrypt algorithm (recommended)
  
      This is considered by many to be the most secure algorithm but you
      must first install the bcrypt library.  Please be warned that
      this library depends on native C code and might cause portability
      issues.
      """
      algorithm = "bcrypt_sha256"
      digest = hashlib.sha256
      library = ("bcrypt", "bcrypt")
      rounds = 12
  
      def salt(self):
          bcrypt = self._load_library()
          return bcrypt.gensalt(self.rounds)
  
      def encode(self, password, salt):
          bcrypt = self._load_library()
          # Hash the password prior to using bcrypt to prevent password
          # truncation as described in #20138.
          if self.digest is not None:
              # Use binascii.hexlify() because a hex encoded bytestring is str.
              password = binascii.hexlify(self.digest(force_bytes(password)).digest())
          else:
              password = force_bytes(password)
  
          data = bcrypt.hashpw(password, salt)
          return "%s$%s" % (self.algorithm, force_text(data))
  
      def verify(self, password, encoded):
          algorithm, data = encoded.split('$', 1)
          assert algorithm == self.algorithm
          encoded_2 = self.encode(password, force_bytes(data))
          return constant_time_compare(encoded, encoded_2)
  
      def safe_summary(self, encoded):
          algorithm, empty, algostr, work_factor, data = encoded.split('$', 4)
          assert algorithm == self.algorithm
          salt, checksum = data[:22], data[22:]
          return OrderedDict([
              ('algorithm', algorithm),
              ('work factor', work_factor),
              ('salt', mask_hash(salt)),
              ('checksum', mask_hash(checksum)),
          ])
  
      def must_update(self, encoded):
          algorithm, empty, algostr, rounds, data = encoded.split('$', 4)
          return int(rounds) != self.rounds
  
      def harden_runtime(self, password, encoded):
          _, data = encoded.split('$', 1)
          salt = data[:29]  # Length of the salt in bcrypt.
          rounds = data.split('$')[2]
          # work factor is logarithmic, adding one doubles the load.
          diff = 2**(self.rounds - int(rounds)) - 1
          while diff > 0:
              self.encode(password, force_bytes(salt))
              diff -= 1
  