summaryrefslogtreecommitdiffstats
path: root/module/lib/beaker/crypto/pbkdf2.py
diff options
context:
space:
mode:
Diffstat (limited to 'module/lib/beaker/crypto/pbkdf2.py')
-rw-r--r--module/lib/beaker/crypto/pbkdf2.py75
1 files changed, 40 insertions, 35 deletions
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')