summaryrefslogtreecommitdiff
path: root/game/python-extra/pbkdf2.py
diff options
context:
space:
mode:
Diffstat (limited to 'game/python-extra/pbkdf2.py')
-rw-r--r--game/python-extra/pbkdf2.py297
1 files changed, 297 insertions, 0 deletions
diff --git a/game/python-extra/pbkdf2.py b/game/python-extra/pbkdf2.py
new file mode 100644
index 0000000..937a99a
--- /dev/null
+++ b/game/python-extra/pbkdf2.py
@@ -0,0 +1,297 @@
+#!/usr/bin/python
+# -*- coding: ascii -*-
+###########################################################################
+# pbkdf2 - PKCS#5 v2.0 Password-Based Key Derivation
+#
+# Copyright (C) 2007-2011 Dwayne C. Litzenberger <dlitz@dlitz.net>
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# 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"
+#
+###########################################################################
+
+__version__ = "1.3"
+__all__ = ['PBKDF2', 'crypt']
+
+from struct import pack
+from random import randint
+import string
+import sys
+
+try:
+ # Use PyCrypto (if available).
+ from Crypto.Hash import HMAC, SHA as SHA1
+except ImportError:
+ # PyCrypto not available. Use the Python standard library.
+ import hmac as HMAC
+ try:
+ from hashlib import sha1 as SHA1
+ except ImportError:
+ # hashlib not available. Use the old sha module.
+ import sha as SHA1
+
+#
+# Python 2.1 thru 3.2 compatibility
+#
+
+if sys.version_info[0] == 2:
+ _0xffffffffL = long(1) << 32
+ def isunicode(s):
+ return isinstance(s, unicode)
+ def isbytes(s):
+ return isinstance(s, str)
+ def isinteger(n):
+ return isinstance(n, (int, long))
+ def b(s):
+ return s
+ def binxor(a, b):
+ return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])
+ def b64encode(data, chars="+/"):
+ tt = string.maketrans("+/", chars)
+ return data.encode('base64').replace("\n", "").translate(tt)
+ from binascii import b2a_hex
+else:
+ _0xffffffffL = 0xffffffff
+ def isunicode(s):
+ return isinstance(s, str)
+ def isbytes(s):
+ return isinstance(s, bytes)
+ def isinteger(n):
+ return isinstance(n, int)
+ def callable(obj):
+ return hasattr(obj, '__call__')
+ def b(s):
+ return s.encode("latin-1")
+ def binxor(a, b):
+ return bytes([x ^ y for (x, y) in zip(a, b)])
+ from base64 import b64encode as _b64encode
+ def b64encode(data, chars="+/"):
+ if isunicode(chars):
+ return _b64encode(data, chars.encode('utf-8')).decode('utf-8')
+ else:
+ return _b64encode(data, chars)
+ from binascii import b2a_hex as _b2a_hex
+ def b2a_hex(s):
+ return _b2a_hex(s).decode('us-ascii')
+ xrange = range
+
+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):
+ 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.new(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 > _0xffffffffL or i < 1:
+ # We could return "" here, but
+ raise OverflowError("derived key too long")
+ block = self.__f(i)
+ blocks.append(block)
+ size += len(block)
+ buf = b("").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 <= _0xffffffffL
+ 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 = binxor(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 isunicode(passphrase):
+ passphrase = passphrase.encode("UTF-8")
+ elif not isbytes(passphrase):
+ raise TypeError("passphrase must be str or unicode")
+ if isunicode(salt):
+ salt = salt.encode("UTF-8")
+ elif not isbytes(salt):
+ raise TypeError("salt must be str or unicode")
+
+ # iterations must be an integer >= 1
+ if not isinteger(iterations):
+ 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 = b("")
+ 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 isunicode(salt):
+ salt = salt.encode('us-ascii').decode('us-ascii')
+ elif isbytes(salt):
+ salt = salt.decode('us-ascii')
+ else:
+ raise TypeError("salt must be a string")
+
+ # word must be a string or unicode (in the latter case, we convert to UTF-8)
+ if isunicode(word):
+ word = word.encode("UTF-8")
+ elif not isbytes(word):
+ 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 = b("").join([pack("@H", randint(0, 0xffff)) for i in range(3)])
+ return b64encode(binarysalt, "./")
+
+# vim:set ts=4 sw=4 sts=4 expandtab: