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: |