text: #!/usr/bin/python
  # -*- coding: ascii -*-
  ###########################################################################
  # PBKDF2.py - PKCS#5 v2.0 Password-Based Key Derivation
  #
  # Copyright (C) 2007, 2008 Dwayne C. Litzenberger <dlitz@dlitz.net>
  # All rights reserved.
  # 
  # Permission to use, copy, modify, and distribute this software and its
  # documentation for any purpose and without fee is hereby granted,
  # provided that the above copyright notice appear in all copies and that
  # both that copyright notice and this permission notice appear in
  # supporting documentation.
  # 
  # THE AUTHOR PROVIDES THIS SOFTWARE ``AS IS'' AND ANY EXPRESSED OR 
  # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
  # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  
  # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 
  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
  # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
  # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  #
  # Country of origin: Canada
  #
  ###########################################################################
  # Sample PBKDF2 usage:
  #   from Crypto.Cipher import AES
  #   from PBKDF2 import PBKDF2
  #   import os
  #
  #   salt = os.urandom(8)    # 64-bit salt
  #   key = PBKDF2("This passphrase is a secret.", salt).read(32) # 256-bit key
  #   iv = os.urandom(16)     # 128-bit IV
  #   cipher = AES.new(key, AES.MODE_CBC, iv)
  #     ...
  #
  # Sample crypt() usage:
  #   from PBKDF2 import crypt
  #   pwhash = crypt("secret")
  #   alleged_pw = raw_input("Enter password: ")
  #   if pwhash == crypt(alleged_pw, pwhash):
  #       print("Password good")
  #   else:
  #       print("Invalid password")
  #
  ###########################################################################
  # History:
  #
  #  2007-07-27 Dwayne C. Litzenberger <dlitz@dlitz.net>
  #   - Initial Release (v1.0)
  #
  #  2007-07-31 Dwayne C. Litzenberger <dlitz@dlitz.net>
  #   - Bugfix release (v1.1)
  #   - SECURITY: The PyCrypto XOR cipher (used, if available, in the _strxor
  #   function in the previous release) silently truncates all keys to 64
  #   bytes.  The way it was used in the previous release, this would only be
  #   problem if the pseudorandom function that returned values larger than
  #   64 bytes (so SHA1, SHA256 and SHA512 are fine), but I don't like
  #   anything that silently reduces the security margin from what is
  #   expected.
  #  
  # 2008-06-17 Dwayne C. Litzenberger <dlitz@dlitz.net>
  #   - Compatibility release (v1.2)
  #   - Add support for older versions of Python (2.2 and 2.3).
  #
  ###########################################################################
  
  __version__ = "1.2"
  
  from struct import pack
  from binascii import b2a_hex
  from random import randint
  import string
  import collections
  
  try:
      # Use PyCrypto (if available)
      from Crypto.Hash import HMAC, SHA as SHA1
  
  except ImportError:
      # PyCrypto not available.  Use the Python standard library.
      import hmac as HMAC
      import sha as SHA1
  
  def strxor(a, b):
      return bytes(bytearray([x ^ y for (x, y) in zip(bytearray(a), bytearray(b))]))
  
  def b64encode(data, chars="+/"):
      tt = string.maketrans("+/", chars)
      return data.encode('base64').replace("\n", "").translate(tt)
  
  class PBKDF2(object):
      """PBKDF2.py : PKCS#5 v2.0 Password-Based Key Derivation
      
      This implementation takes a passphrase and a salt (and optionally an
      iteration count, a digest module, and a MAC module) and provides a
      file-like object from which an arbitrarily-sized key can be read.
  
      If the passphrase and/or salt are unicode objects, they are encoded as
      UTF-8 before they are processed.
  
      The idea behind PBKDF2 is to derive a cryptographic key from a
      passphrase and a salt.
      
      PBKDF2 may also be used as a strong salted password hash.  The
      'crypt' function is provided for that purpose.
      
      Remember: Keys generated using PBKDF2 are only as strong as the
      passphrases they are derived from.
      """
  
      def __init__(self, passphrase, salt, iterations=1000,
                   digestmodule=SHA1, macmodule=HMAC):
          self.__macmodule = macmodule
          self.__digestmodule = digestmodule
          self._setup(passphrase, salt, iterations, self._pseudorandom)
  
      def _pseudorandom(self, key, msg):
          """Pseudorandom function.  e.g. HMAC-SHA1"""
          return self.__macmodule.new(key=key, msg=msg,
              digestmod=self.__digestmodule).digest()
      
      def read(self, bytes):
          """Read the specified number of key bytes."""
          if self.closed:
              raise ValueError("file-like object is closed")
  
          size = len(self.__buf)
          blocks = [self.__buf]
          i = self.__blockNum
          while size < bytes:
              i += 1
              if i > 0xffffffff or i < 1:
                  # We could return "" here, but 
                  raise OverflowError("derived key too long")
              block = self.__f(i)
              blocks.append(block)
              size += len(block)
          buf = b"".join(blocks)
          retval = buf[:bytes]
          self.__buf = buf[bytes:]
          self.__blockNum = i
          return retval
      
      def __f(self, i):
          # i must fit within 32 bits
          assert 1 <= i <= 0xffffffff
          U = self.__prf(self.__passphrase, self.__salt + pack("!L", i))
          result = U
          for j in range(2, 1+self.__iterations):
              U = self.__prf(self.__passphrase, U)
              result = strxor(result, U)
          return result
      
      def hexread(self, octets):
          """Read the specified number of octets. Return them as hexadecimal.
  
          Note that len(obj.hexread(n)) == 2*n.
          """
          return "{}".format(b2a_hex(self.read(octets)).decode('ascii'))
  
      def _setup(self, passphrase, salt, iterations, prf):
          # Sanity checks:
          
          # passphrase and salt must be str or unicode (in the latter
          # case, we convert to UTF-8)
          if isinstance(passphrase, bytes):
              pass
          elif hasattr(passphrase, "encode") and callable(getattr(passphrase, "encode")):
              passphrase = passphrase.encode("UTF-8")
          if not isinstance(passphrase, bytes):
              raise TypeError("passphrase must be str or unicode")
          if isinstance(salt, bytes):
              pass
          elif hasattr(salt, "encode") and callable(getattr(salt, "encode")):
              salt = salt.encode("UTF-8")
          if not isinstance(salt, bytes):
              raise TypeError("salt must be str or unicode")
  
          # iterations must be an integer >= 1
          if not isinstance(iterations, int):
              raise TypeError("iterations must be an integer")
          if iterations < 1:
              raise ValueError("iterations must be at least 1")
          
          # prf must be callable
          if not isinstance(prf, collections.Callable):
              raise TypeError("prf must be callable")
  
          self.__passphrase = passphrase
          self.__salt = salt
          self.__iterations = iterations
          self.__prf = prf
          self.__blockNum = 0
          self.__buf = b""
          self.closed = False
      
      def close(self):
          """Close the stream."""
          if not self.closed:
              del self.__passphrase
              del self.__salt
              del self.__iterations
              del self.__prf
              del self.__blockNum
              del self.__buf
              self.closed = True
  
  def crypt(word, salt=None, iterations=None):
      """PBKDF2-based unix crypt(3) replacement.
      
      The number of iterations specified in the salt overrides the 'iterations'
      parameter.
  
      The effective hash length is 192 bits.
      """
      
      # Generate a (pseudo-)random salt if the user hasn't provided one.
      if salt is None:
          salt = _makesalt()
  
      # salt must be a string or the us-ascii subset of unicode
      if isinstance(salt, bytes):
          pass
      elif hasattr(salt, "encode") and callable(getattr(salt, "encode")):
          salt = salt.encode("us-ascii")
      if not isinstance(salt, bytes):
          raise TypeError("salt must be a string")
  
      # word must be a string or unicode (in the latter case, we convert to UTF-8)
      if isinstance(word, bytes):
          pass
      elif hasattr(word, "encode") and callable(getattr(word, "encode")):
          word = word.encode("UTF-8")
      if not isinstance(word, bytes):
          raise TypeError("word must be a string or unicode")
  
      # Try to extract the real salt and iteration count from the salt
      if salt.startswith("$p5k2$"):
          (iterations, salt, dummy) = salt.split("$")[2:5]
          if iterations == "":
              iterations = 400
          else:
              converted = int(iterations, 16)
              if iterations != "%x" % converted:  # lowercase hex, minimum digits
                  raise ValueError("Invalid salt")
              iterations = converted
              if not (iterations >= 1):
                  raise ValueError("Invalid salt")
      
      # Make sure the salt matches the allowed character set
      allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
      for ch in salt:
          if ch not in allowed:
              raise ValueError("Illegal character %r in salt" % (ch,))
  
      if iterations is None or iterations == 400:
          iterations = 400
          salt = "$p5k2$$" + salt
      else:
          salt = "$p5k2$%x$%s" % (iterations, salt)
      rawhash = PBKDF2(word, salt, iterations).read(24)
      return salt + "$" + b64encode(rawhash, "./")
  
  # Add crypt as a static method of the PBKDF2 class
  # This makes it easier to do "from PBKDF2 import PBKDF2" and still use
  # crypt.
  PBKDF2.crypt = staticmethod(crypt)
  
  def _makesalt():
      """Return a 48-bit pseudorandom salt for crypt().
      
      This function is not suitable for generating cryptographic secrets.
      """
      binarysalt = "".join([pack("@H", randint(0, 0xffff)) for i in range(3)])
      return b64encode(binarysalt, "./")
  
  def test_pbkdf2():
      """Module self-test"""
      from binascii import a2b_hex
      
      #
      # Test vectors from RFC 3962
      #
  
      # Test 1
      result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1).read(16)
      expected = a2b_hex("cdedb5281bb2f801565a1122b2563515")
      if result != expected:
          raise RuntimeError("self-test failed")
  
      # Test 2
      result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1200).hexread(32)
      expected = ("5c08eb61fdf71e4e4ec3cf6ba1f5512b"
                  "a7e52ddbc5e5142f708a31e2e62b1e13")
      if result != expected:
          raise RuntimeError("self-test failed")
  
      # Test 3
      result = PBKDF2("X"*64, "pass phrase equals block size", 1200).hexread(32)
      expected = ("139c30c0966bc32ba55fdbf212530ac9"
                  "c5ec59f1a452f5cc9ad940fea0598ed1")
      if result != expected:
          raise RuntimeError("self-test failed")
      
      # Test 4
      result = PBKDF2("X"*65, "pass phrase exceeds block size", 1200).hexread(32)
      expected = ("9ccad6d468770cd51b10e6a68721be61"
                  "1a8b4d282601db3b36be9246915ec82a")
      if result != expected:
          raise RuntimeError("self-test failed")
      
      #
      # Other test vectors
      #
      
      # Chunked read
      f = PBKDF2("kickstart", "workbench", 256)
      result = f.read(17)
      result += f.read(17)
      result += f.read(1)
      result += f.read(2)
      result += f.read(3)
      expected = PBKDF2("kickstart", "workbench", 256).read(40)
      if result != expected:
          raise RuntimeError("self-test failed")
      
      #
      # crypt() test vectors
      #
  
      # crypt 1
      result = crypt("cloadm", "exec")
      expected = '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql'
      if result != expected:
          raise RuntimeError("self-test failed")
      
      # crypt 2
      result = crypt("gnu", '$p5k2$c$u9HvcT4d$.....')
      expected = '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g'
      if result != expected:
          raise RuntimeError("self-test failed")
  
      # crypt 3
      result = crypt("dcl", "tUsch7fU", iterations=13)
      expected = "$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL"
      if result != expected:
          raise RuntimeError("self-test failed")
      
      # crypt 4 (unicode)
      result = crypt('\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2',
          '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ')
      expected = '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ'
      if result != expected:
          raise RuntimeError("self-test failed")
  
  if __name__ == '__main__':
      test_pbkdf2()
  
  # vim:set ts=4 sw=4 sts=4 expandtab:
  