summaryrefslogtreecommitdiffstats
path: root/module/lib/beaker/crypto
diff options
context:
space:
mode:
Diffstat (limited to 'module/lib/beaker/crypto')
-rw-r--r--module/lib/beaker/crypto/__init__.py8
-rw-r--r--module/lib/beaker/crypto/jcecrypto.py2
-rw-r--r--module/lib/beaker/crypto/nsscrypto.py45
-rw-r--r--module/lib/beaker/crypto/pbkdf2.py75
-rw-r--r--module/lib/beaker/crypto/pycrypto.py17
-rw-r--r--module/lib/beaker/crypto/util.py4
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