summaryrefslogtreecommitdiffstats
path: root/lib/Python/Lib/beaker/crypto
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Python/Lib/beaker/crypto')
-rw-r--r--lib/Python/Lib/beaker/crypto/__init__.py40
-rw-r--r--lib/Python/Lib/beaker/crypto/jcecrypto.py30
-rw-r--r--lib/Python/Lib/beaker/crypto/pbkdf2.py342
-rw-r--r--lib/Python/Lib/beaker/crypto/pycrypto.py31
-rw-r--r--lib/Python/Lib/beaker/crypto/util.py30
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