diff options
Diffstat (limited to 'lib/Python/Lib/beaker/crypto')
| -rw-r--r-- | lib/Python/Lib/beaker/crypto/__init__.py | 40 | ||||
| -rw-r--r-- | lib/Python/Lib/beaker/crypto/jcecrypto.py | 30 | ||||
| -rw-r--r-- | lib/Python/Lib/beaker/crypto/pbkdf2.py | 342 | ||||
| -rw-r--r-- | lib/Python/Lib/beaker/crypto/pycrypto.py | 31 | ||||
| -rw-r--r-- | lib/Python/Lib/beaker/crypto/util.py | 30 | 
5 files changed, 473 insertions, 0 deletions
| diff --git a/lib/Python/Lib/beaker/crypto/__init__.py b/lib/Python/Lib/beaker/crypto/__init__.py new file mode 100644 index 000000000..3e26b0c13 --- /dev/null +++ b/lib/Python/Lib/beaker/crypto/__init__.py @@ -0,0 +1,40 @@ +from warnings import warn + +from beaker.crypto.pbkdf2 import PBKDF2, strxor +from beaker.crypto.util import hmac, sha1, hmac_sha1, md5 +from beaker import util + +keyLength = None + +if util.jython: +    try: +        from beaker.crypto.jcecrypto import getKeyLength, aesEncrypt +        keyLength = getKeyLength() +    except ImportError: +        pass +else: +    try: +        from beaker.crypto.pycrypto import getKeyLength, aesEncrypt, aesDecrypt +        keyLength = getKeyLength() +    except ImportError: +        pass + +if not keyLength: +    has_aes = False +else: +    has_aes = True + +if has_aes and keyLength < 32: +    warn('Crypto implementation only supports key lengths up to %d bits. ' +         'Generated session cookies may be incompatible with other ' +         'environments' % (keyLength * 8)) + + +def generateCryptoKeys(master_key, salt, iterations): +    # NB: We XOR parts of the keystream into the randomly-generated parts, just +    # in case os.urandom() isn't as random as it should be.  Note that if +    # os.urandom() returns truly random data, this will have no effect on the +    # overall security. +    keystream = PBKDF2(master_key, salt, iterations=iterations) +    cipher_key = keystream.read(keyLength) +    return cipher_key diff --git a/lib/Python/Lib/beaker/crypto/jcecrypto.py b/lib/Python/Lib/beaker/crypto/jcecrypto.py new file mode 100644 index 000000000..4062d513e --- /dev/null +++ b/lib/Python/Lib/beaker/crypto/jcecrypto.py @@ -0,0 +1,30 @@ +""" +Encryption module that uses the Java Cryptography Extensions (JCE). + +Note that in default installations of the Java Runtime Environment, the +maximum key length is limited to 128 bits due to US export +restrictions. This makes the generated keys incompatible with the ones +generated by pycryptopp, which has no such restrictions. To fix this, +download the "Unlimited Strength Jurisdiction Policy Files" from Sun, +which will allow encryption using 256 bit AES keys. +""" +from javax.crypto import Cipher +from javax.crypto.spec import SecretKeySpec, IvParameterSpec + +import jarray + +# Initialization vector filled with zeros +_iv = IvParameterSpec(jarray.zeros(16, 'b')) + +def aesEncrypt(data, key): +    cipher = Cipher.getInstance('AES/CTR/NoPadding') +    skeySpec = SecretKeySpec(key, 'AES') +    cipher.init(Cipher.ENCRYPT_MODE, skeySpec, _iv) +    return cipher.doFinal(data).tostring() + +# magic. +aesDecrypt = aesEncrypt + +def getKeyLength(): +    maxlen = Cipher.getMaxAllowedKeyLength('AES/CTR/NoPadding') +    return min(maxlen, 256) / 8 diff --git a/lib/Python/Lib/beaker/crypto/pbkdf2.py b/lib/Python/Lib/beaker/crypto/pbkdf2.py new file mode 100644 index 000000000..96dc5fbb2 --- /dev/null +++ b/lib/Python/Lib/beaker/crypto/pbkdf2.py @@ -0,0 +1,342 @@ +#!/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 <= 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: diff --git a/lib/Python/Lib/beaker/crypto/pycrypto.py b/lib/Python/Lib/beaker/crypto/pycrypto.py new file mode 100644 index 000000000..a3eb4d9db --- /dev/null +++ b/lib/Python/Lib/beaker/crypto/pycrypto.py @@ -0,0 +1,31 @@ +"""Encryption module that uses pycryptopp or pycrypto""" +try: +    # Pycryptopp is preferred over Crypto because Crypto has had +    # various periods of not being maintained, and pycryptopp uses +    # the Crypto++ library which is generally considered the 'gold standard' +    # of crypto implementations +    from pycryptopp.cipher import aes + +    def aesEncrypt(data, key): +        cipher = aes.AES(key) +        return cipher.process(data) +     +    # magic. +    aesDecrypt = aesEncrypt +     +except ImportError: +    from Crypto.Cipher import AES + +    def aesEncrypt(data, key): +        cipher = AES.new(key) +         +        data = data + (" " * (16 - (len(data) % 16))) +        return cipher.encrypt(data) + +    def aesDecrypt(data, key): +        cipher = AES.new(key) + +        return cipher.decrypt(data).rstrip() + +def getKeyLength(): +    return 32 diff --git a/lib/Python/Lib/beaker/crypto/util.py b/lib/Python/Lib/beaker/crypto/util.py new file mode 100644 index 000000000..d97e8ce6f --- /dev/null +++ b/lib/Python/Lib/beaker/crypto/util.py @@ -0,0 +1,30 @@ +from warnings import warn +from beaker import util + + +try: +    # Use PyCrypto (if available) +    from Crypto.Hash import HMAC as hmac, SHA as hmac_sha1 +    sha1 = hmac_sha1.new +     +except ImportError: +     +    # PyCrypto not available.  Use the Python standard library. +    import hmac + +    # When using the stdlib, we have to make sure the hmac version and sha +    # version are compatible +    if util.py24: +        from sha import sha as sha1 +        import sha as hmac_sha1 +    else: +        # NOTE: We have to use the callable with hashlib (hashlib.sha1), +        # otherwise hmac only accepts the sha module object itself +        from hashlib import sha1 +        hmac_sha1 = sha1 + + +if util.py24: +    from md5 import md5 +else: +    from hashlib import md5 | 
