diff options
-rw-r--r-- | game/python-extra/pbkdf2.py | 297 |
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: |