123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- #!/usr/bin/python
- # -*- coding: ascii -*-
- ###########################################################################
- # pbkdf2 - PKCS#5 v2.0 Password-Based Key Derivation
- #
- # Copyright (C) 2007-2011 Dwayne C. Litzenberger <dlitz@dlitz.net>
- #
- # Permission is hereby granted, free of charge, to any person obtaining
- # a copy of this software and associated documentation files (the
- # "Software"), to deal in the Software without restriction, including
- # without limitation the rights to use, copy, modify, merge, publish,
- # distribute, sublicense, and/or sell copies of the Software, and to
- # permit persons to whom the Software is furnished to do so, subject to
- # the following conditions:
- #
- # The above copyright notice and this permission notice shall be
- # included in all copies or substantial portions of the Software.
- #
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- #
- # 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"
- #
- ###########################################################################
- __version__ = "1.3"
- __all__ = ['PBKDF2', 'crypt']
- from struct import pack
- from random import randint
- import string
- import sys
- 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
- try:
- from hashlib import sha1 as SHA1
- except ImportError:
- # hashlib not available. Use the old sha module.
- import sha as SHA1
- #
- # Python 2.1 thru 3.2 compatibility
- #
- if sys.version_info[0] == 2:
- _0xffffffffL = long(1) << 32
- def isunicode(s):
- return isinstance(s, unicode)
- def isbytes(s):
- return isinstance(s, str)
- def isinteger(n):
- return isinstance(n, (int, long))
- def b(s):
- return s
- def binxor(a, b):
- return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])
- def b64encode(data, chars="+/"):
- tt = string.maketrans("+/", chars)
- return data.encode('base64').replace("\n", "").translate(tt)
- from binascii import b2a_hex
- else:
- _0xffffffffL = 0xffffffff
- def isunicode(s):
- return isinstance(s, str)
- def isbytes(s):
- return isinstance(s, bytes)
- def isinteger(n):
- return isinstance(n, int)
- def callable(obj):
- return hasattr(obj, '__call__')
- def b(s):
- return s.encode("latin-1")
- def binxor(a, b):
- return bytes([x ^ y for (x, y) in zip(a, b)])
- from base64 import b64encode as _b64encode
- def b64encode(data, chars="+/"):
- if isunicode(chars):
- return _b64encode(data, chars.encode('utf-8')).decode('utf-8')
- else:
- return _b64encode(data, chars)
- from binascii import b2a_hex as _b2a_hex
- def b2a_hex(s):
- return _b2a_hex(s).decode('us-ascii')
- xrange = range
- 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 > _0xffffffffL 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 <= _0xffffffffL
- U = self.__prf(self.__passphrase, self.__salt + pack("!L", i))
- result = U
- for j in xrange(2, 1+self.__iterations):
- U = self.__prf(self.__passphrase, U)
- result = binxor(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 b2a_hex(self.read(octets))
- 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 isunicode(passphrase):
- passphrase = passphrase.encode("UTF-8")
- elif not isbytes(passphrase):
- raise TypeError("passphrase must be str or unicode")
- if isunicode(salt):
- salt = salt.encode("UTF-8")
- elif not isbytes(salt):
- raise TypeError("salt must be str or unicode")
- # iterations must be an integer >= 1
- if not isinteger(iterations):
- raise TypeError("iterations must be an integer")
- if iterations < 1:
- raise ValueError("iterations must be at least 1")
- # prf must be callable
- if not callable(prf):
- 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 isunicode(salt):
- salt = salt.encode('us-ascii').decode('us-ascii')
- elif isbytes(salt):
- salt = salt.decode('us-ascii')
- else:
- raise TypeError("salt must be a string")
- # word must be a string or unicode (in the latter case, we convert to UTF-8)
- if isunicode(word):
- word = word.encode("UTF-8")
- elif not isbytes(word):
- 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 = b("").join([pack("@H", randint(0, 0xffff)) for i in range(3)])
- return b64encode(binarysalt, "./")
-
- if __name__ == '__main__':
- import unittest
- class TestPBKDF2(unittest.TestCase):
- def test_pbkdf2(self):
- """Module self-test"""
- from binascii import a2b_hex as _a2b_hex
- def a2b_hex(s):
- return _a2b_hex(b(s))
- #
- # Test vectors from RFC 3962
- #
- # Test 1
- result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1).read(16)
- expected = a2b_hex("cdedb5281bb2f801565a1122b2563515")
- self.assertEqual(expected, result)
- # Test 2
- result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1200).hexread(32)
- expected = ("5c08eb61fdf71e4e4ec3cf6ba1f5512b"
- "a7e52ddbc5e5142f708a31e2e62b1e13")
- self.assertEqual(expected, result)
- # Test 3
- result = PBKDF2("X"*64, "pass phrase equals block size", 1200).hexread(32)
- expected = ("139c30c0966bc32ba55fdbf212530ac9"
- "c5ec59f1a452f5cc9ad940fea0598ed1")
- self.assertEqual(expected, result)
- # Test 4
- result = PBKDF2("X"*65, "pass phrase exceeds block size", 1200).hexread(32)
- expected = ("9ccad6d468770cd51b10e6a68721be61"
- "1a8b4d282601db3b36be9246915ec82a")
- self.assertEqual(expected, result)
- #
- # 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)
- self.assertEqual(expected, result)
- #
- # crypt() test vectors
- #
- # crypt 1
- result = crypt("cloadm", "exec")
- expected = '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql'
- self.assertEqual(expected, result)
- # crypt 2
- result = crypt("gnu", '$p5k2$c$u9HvcT4d$.....')
- expected = '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g'
- self.assertEqual(expected, result)
- # crypt 3
- result = crypt("dcl", "tUsch7fU", iterations=13)
- expected = "$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL"
- self.assertEqual(expected, result)
- # crypt 4 (unicode)
- result = crypt(b('\xce\x99\xcf\x89\xce\xb1\xce\xbd\xce\xbd\xce\xb7\xcf\x82').decode('utf-8'),
- '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ')
- expected = '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ'
- self.assertEqual(expected, result)
- # crypt 5 (UTF-8 bytes)
- result = crypt(b('\xce\x99\xcf\x89\xce\xb1\xce\xbd\xce\xbd\xce\xb7\xcf\x82'),
- '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ')
- expected = '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ'
- self.assertEqual(expected, result)
- def test_crypt(self):
- result = crypt("secret")
- self.assertEqual(result[:6], "$p5k2$")
- result = crypt("secret", "XXXXXXXX")
- expected = '$p5k2$$XXXXXXXX$L9mVVdq7upotdvtGvXTDTez3FIu3z0uG'
- self.assertEqual(expected, result)
- # 400 iterations (the default for crypt)
- result = crypt("secret", "XXXXXXXX", 400)
- expected = '$p5k2$$XXXXXXXX$L9mVVdq7upotdvtGvXTDTez3FIu3z0uG'
- self.assertEqual(expected, result)
- # 400 iterations (keyword argument)
- result = crypt("spam", "FRsH3HJB", iterations=400)
- expected = '$p5k2$$FRsH3HJB$SgRWDNmB2LukCy0OTal6LYLHZVgtOi7s'
- self.assertEqual(expected, result)
- # 1000 iterations
- result = crypt("spam", "H0NX9mT/", iterations=1000)
- expected = '$p5k2$3e8$H0NX9mT/$wk/sE8vv6OMKuMaqazCJYDSUhWY9YB2J'
- self.assertEqual(expected, result)
- # 1000 iterations (iterations count taken from salt parameter)
- expected = '$p5k2$3e8$H0NX9mT/$wk/sE8vv6OMKuMaqazCJYDSUhWY9YB2J'
- result = crypt("spam", expected)
- self.assertEqual(expected, result)
- unittest.main(verbosity=2)
- # vim:set ts=4 sw=4 sts=4 expandtab:
|