diff options
| author | 2014-09-08 00:29:57 +0200 | |
|---|---|---|
| committer | 2014-09-14 11:02:23 +0200 | |
| commit | 68d662e689cd42687341c550fb6ebb74e6968d21 (patch) | |
| tree | 486cef41bd928b8db704894233b2cef94a6e346f /pyload/lib/beaker/crypto/pbkdf2.py | |
| parent | save_join -> safe_join & save_path -> safe_filename (diff) | |
| download | pyload-68d662e689cd42687341c550fb6ebb74e6968d21.tar.xz | |
module -> pyload
Diffstat (limited to 'pyload/lib/beaker/crypto/pbkdf2.py')
| -rw-r--r-- | pyload/lib/beaker/crypto/pbkdf2.py | 347 | 
1 files changed, 347 insertions, 0 deletions
| diff --git a/pyload/lib/beaker/crypto/pbkdf2.py b/pyload/lib/beaker/crypto/pbkdf2.py new file mode 100644 index 000000000..71df22198 --- /dev/null +++ b/pyload/lib/beaker/crypto/pbkdf2.py @@ -0,0 +1,347 @@ +#!/usr/bin/python +# -*- coding: ascii -*- +########################################################################### +# PBKDF2.py - PKCS#5 v2.0 Password-Based Key Derivation +# +# Copyright (C) 2007 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. +# +########################################################################### + +__version__ = "1.1" + +from struct import pack +from binascii import b2a_hex +from random import randint + +from base64 import b64encode + +from beaker.crypto.util import hmac as HMAC, hmac_sha1 as SHA1 + + +def strxor(a, b): +    return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)]) + + +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): +        if not callable(macmodule): +            macmodule = macmodule.new +        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(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: +                # We could return "" here, but +                raise OverflowError("derived key too long") +            block = self.__f(i) +            blocks.append(block) +            size += len(block) +        buf = "".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 and i <= 0xffffffff) +        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 = 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 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 isinstance(passphrase, unicode): +            passphrase = passphrase.encode("UTF-8") +        if not isinstance(passphrase, str): +            raise TypeError("passphrase must be str or unicode") +        if isinstance(salt, unicode): +            salt = salt.encode("UTF-8") +        if not isinstance(salt, str): +            raise TypeError("salt must be str or unicode") + +        # iterations must be an integer >= 1 +        if not isinstance(iterations, (int, long)): +            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 = "" +        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, unicode): +        salt = salt.encode("us-ascii") +    if not isinstance(salt, str): +        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, unicode): +        word = word.encode("UTF-8") +    if not isinstance(word, str): +        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(u'\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: | 
