text: import hashlib
  from django.utils.encoding import smart_str
  from django.utils.crypto import constant_time_compare
  
  UNUSABLE_PASSWORD = '!' # This will never be a valid hash
  
  def get_hexdigest(algorithm, salt, raw_password):
      """
      Returns a string of the hexdigest of the given plaintext password and salt
      using the given algorithm ('md5', 'sha1' or 'crypt').
      """
      raw_password, salt = smart_str(raw_password), smart_str(salt)
      if algorithm == 'crypt':
          try:
              import crypt
          except ImportError:
              raise ValueError('"crypt" password algorithm not supported in this environment')
          return crypt.crypt(raw_password, salt)
  
      if algorithm == 'md5':
          return hashlib.md5(salt + raw_password).hexdigest()
      elif algorithm == 'sha1':
          return hashlib.sha1(salt + raw_password).hexdigest()
      raise ValueError("Got unknown password algorithm type in password.")
  
  def get_random_string(length=12, allowed_chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'):
      """
      Returns a random string of length characters from the set of a-z, A-Z, 0-9
      for use as a salt.
  
      The default length of 12 with the a-z, A-Z, 0-9 character set returns
      a 71-bit salt. log_2((26+26+10)^12) =~ 71 bits
      """
      import random
      try:
          random = random.SystemRandom()
      except NotImplementedError:
          pass
      return ''.join([random.choice(allowed_chars) for i in range(length)])
  
  def check_password(raw_password, enc_password):
      """
      Returns a boolean of whether the raw_password was correct. Handles
      encryption formats behind the scenes.
      """
      parts = enc_password.split('$')
      if len(parts) != 3:
          return False
      algo, salt, hsh = parts
      return constant_time_compare(hsh, get_hexdigest(algo, salt, raw_password))
  
  def is_password_usable(encoded_password):
      return encoded_password is not None and encoded_password != UNUSABLE_PASSWORD
  
  def make_password(algo, raw_password):
      """
      Produce a new password string in this format: algorithm$salt$hash
      """
      if raw_password is None:
          return UNUSABLE_PASSWORD
      salt = get_random_string()
      hsh = get_hexdigest(algo, salt, raw_password)
      return '%s$%s$%s' % (algo, salt, hsh)
  