diff options
Diffstat (limited to 'module/lib/beaker/crypto')
| -rw-r--r-- | module/lib/beaker/crypto/__init__.py | 8 | ||||
| -rw-r--r-- | module/lib/beaker/crypto/jcecrypto.py | 2 | ||||
| -rw-r--r-- | module/lib/beaker/crypto/nsscrypto.py | 45 | ||||
| -rw-r--r-- | module/lib/beaker/crypto/pbkdf2.py | 75 | ||||
| -rw-r--r-- | module/lib/beaker/crypto/pycrypto.py | 17 | ||||
| -rw-r--r-- | module/lib/beaker/crypto/util.py | 4 | 
6 files changed, 105 insertions, 46 deletions
| diff --git a/module/lib/beaker/crypto/__init__.py b/module/lib/beaker/crypto/__init__.py index 3e26b0c13..ac13da527 100644 --- a/module/lib/beaker/crypto/__init__.py +++ b/module/lib/beaker/crypto/__init__.py @@ -14,10 +14,14 @@ if util.jython:          pass  else:      try: -        from beaker.crypto.pycrypto import getKeyLength, aesEncrypt, aesDecrypt +        from beaker.crypto.nsscrypto import getKeyLength, aesEncrypt, aesDecrypt          keyLength = getKeyLength()      except ImportError: -        pass +        try: +            from beaker.crypto.pycrypto import getKeyLength, aesEncrypt, aesDecrypt +            keyLength = getKeyLength() +        except ImportError: +            pass  if not keyLength:      has_aes = False diff --git a/module/lib/beaker/crypto/jcecrypto.py b/module/lib/beaker/crypto/jcecrypto.py index 4062d513e..ce313d6e1 100644 --- a/module/lib/beaker/crypto/jcecrypto.py +++ b/module/lib/beaker/crypto/jcecrypto.py @@ -16,6 +16,7 @@ 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') @@ -25,6 +26,7 @@ def aesEncrypt(data, key):  # magic.  aesDecrypt = aesEncrypt +  def getKeyLength():      maxlen = Cipher.getMaxAllowedKeyLength('AES/CTR/NoPadding')      return min(maxlen, 256) / 8 diff --git a/module/lib/beaker/crypto/nsscrypto.py b/module/lib/beaker/crypto/nsscrypto.py new file mode 100644 index 000000000..3a7797877 --- /dev/null +++ b/module/lib/beaker/crypto/nsscrypto.py @@ -0,0 +1,45 @@ +"""Encryption module that uses nsscrypto""" +import nss.nss + +nss.nss.nss_init_nodb() + +# Apparently the rest of beaker doesn't care about the particluar cipher, +# mode and padding used. +# NOTE: A constant IV!!! This is only secure if the KEY is never reused!!! +_mech = nss.nss.CKM_AES_CBC_PAD +_iv = '\0' * nss.nss.get_iv_length(_mech) + +def aesEncrypt(data, key): +    slot = nss.nss.get_best_slot(_mech) + +    key_obj = nss.nss.import_sym_key(slot, _mech, nss.nss.PK11_OriginGenerated, +                                     nss.nss.CKA_ENCRYPT, nss.nss.SecItem(key)) + +    param = nss.nss.param_from_iv(_mech, nss.nss.SecItem(_iv)) +    ctx = nss.nss.create_context_by_sym_key(_mech, nss.nss.CKA_ENCRYPT, key_obj, +                                            param) +    l1 = ctx.cipher_op(data) +    # Yes, DIGEST.  This needs fixing in NSS, but apparently nobody (including +    # me :( ) cares enough. +    l2 = ctx.digest_final() + +    return l1 + l2 + +def aesDecrypt(data, key): +    slot = nss.nss.get_best_slot(_mech) + +    key_obj = nss.nss.import_sym_key(slot, _mech, nss.nss.PK11_OriginGenerated, +                                     nss.nss.CKA_DECRYPT, nss.nss.SecItem(key)) + +    param = nss.nss.param_from_iv(_mech, nss.nss.SecItem(_iv)) +    ctx = nss.nss.create_context_by_sym_key(_mech, nss.nss.CKA_DECRYPT, key_obj, +                                            param) +    l1 = ctx.cipher_op(data) +    # Yes, DIGEST.  This needs fixing in NSS, but apparently nobody (including +    # me :( ) cares enough. +    l2 = ctx.digest_final() + +    return l1 + l2 + +def getKeyLength(): +    return 32 diff --git a/module/lib/beaker/crypto/pbkdf2.py b/module/lib/beaker/crypto/pbkdf2.py index 96dc5fbb2..71df22198 100644 --- a/module/lib/beaker/crypto/pbkdf2.py +++ b/module/lib/beaker/crypto/pbkdf2.py @@ -5,22 +5,22 @@  #  # 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,  +# +# 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  +# 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 @@ -74,12 +74,14 @@ 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. @@ -89,10 +91,10 @@ class PBKDF2(object):      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.      """ @@ -109,7 +111,7 @@ class PBKDF2(object):          """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: @@ -121,7 +123,7 @@ class PBKDF2(object):          while size < bytes:              i += 1              if i > 0xffffffff: -                # We could return "" here, but  +                # We could return "" here, but                  raise OverflowError("derived key too long")              block = self.__f(i)              blocks.append(block) @@ -131,17 +133,17 @@ class PBKDF2(object):          self.__buf = buf[bytes:]          self.__blockNum = i          return retval -     +      def __f(self, i):          # i must fit within 32 bits -        assert (1 <= i <= 0xffffffff) +        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): +        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. @@ -151,7 +153,7 @@ class PBKDF2(object):      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): @@ -168,7 +170,7 @@ class PBKDF2(object):              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") @@ -180,7 +182,7 @@ class PBKDF2(object):          self.__blockNum = 0          self.__buf = ""          self.closed = False -     +      def close(self):          """Close the stream."""          if not self.closed: @@ -192,15 +194,16 @@ class PBKDF2(object):              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() @@ -229,7 +232,7 @@ def crypt(word, salt=None, iterations=None):              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: @@ -249,18 +252,20 @@ def crypt(word, salt=None, iterations=None):  # 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      # @@ -279,23 +284,23 @@ def test_pbkdf2():          raise RuntimeError("self-test failed")      # Test 3 -    result = PBKDF2("X"*64, "pass phrase equals block size", 1200).hexread(32) +    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) +    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) @@ -306,7 +311,7 @@ def test_pbkdf2():      expected = PBKDF2("kickstart", "workbench", 256).read(40)      if result != expected:          raise RuntimeError("self-test failed") -     +      #      # crypt() test vectors      # @@ -316,7 +321,7 @@ def test_pbkdf2():      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' @@ -328,7 +333,7 @@ def test_pbkdf2():      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') diff --git a/module/lib/beaker/crypto/pycrypto.py b/module/lib/beaker/crypto/pycrypto.py index a3eb4d9db..6657bff56 100644 --- a/module/lib/beaker/crypto/pycrypto.py +++ b/module/lib/beaker/crypto/pycrypto.py @@ -9,23 +9,26 @@ try:      def aesEncrypt(data, key):          cipher = aes.AES(key)          return cipher.process(data) -     +      # magic.      aesDecrypt = aesEncrypt -     +  except ImportError:      from Crypto.Cipher import AES +    from Crypto.Util import Counter      def aesEncrypt(data, key): -        cipher = AES.new(key) -         -        data = data + (" " * (16 - (len(data) % 16))) +        cipher = AES.new(key, AES.MODE_CTR, +                         counter=Counter.new(128, initial_value=0)) +          return cipher.encrypt(data)      def aesDecrypt(data, key): -        cipher = AES.new(key) +        cipher = AES.new(key, AES.MODE_CTR, +                         counter=Counter.new(128, initial_value=0)) +        return cipher.decrypt(data) + -        return cipher.decrypt(data).rstrip()  def getKeyLength():      return 32 diff --git a/module/lib/beaker/crypto/util.py b/module/lib/beaker/crypto/util.py index d97e8ce6f..7f96ac856 100644 --- a/module/lib/beaker/crypto/util.py +++ b/module/lib/beaker/crypto/util.py @@ -6,9 +6,9 @@ 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 | 
