root/galaxy-central/eggs/Beaker-1.4-py2.6.egg/beaker/crypto/pbkdf2.py @ 3

リビジョン 3, 12.2 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1#!/usr/bin/python
2# -*- coding: ascii -*-
3###########################################################################
4# PBKDF2.py - PKCS#5 v2.0 Password-Based Key Derivation
5#
6# Copyright (C) 2007 Dwayne C. Litzenberger <dlitz@dlitz.net>
7# All rights reserved.
8#
9# Permission to use, copy, modify, and distribute this software and its
10# documentation for any purpose and without fee is hereby granted,
11# provided that the above copyright notice appear in all copies and that
12# both that copyright notice and this permission notice appear in
13# supporting documentation.
14#
15# THE AUTHOR PROVIDES THIS SOFTWARE ``AS IS'' AND ANY EXPRESSED OR
16# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
18# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25#
26# Country of origin: Canada
27#
28###########################################################################
29# Sample PBKDF2 usage:
30#   from Crypto.Cipher import AES
31#   from PBKDF2 import PBKDF2
32#   import os
33#
34#   salt = os.urandom(8)    # 64-bit salt
35#   key = PBKDF2("This passphrase is a secret.", salt).read(32) # 256-bit key
36#   iv = os.urandom(16)     # 128-bit IV
37#   cipher = AES.new(key, AES.MODE_CBC, iv)
38#     ...
39#
40# Sample crypt() usage:
41#   from PBKDF2 import crypt
42#   pwhash = crypt("secret")
43#   alleged_pw = raw_input("Enter password: ")
44#   if pwhash == crypt(alleged_pw, pwhash):
45#       print "Password good"
46#   else:
47#       print "Invalid password"
48#
49###########################################################################
50# History:
51#
52#  2007-07-27 Dwayne C. Litzenberger <dlitz@dlitz.net>
53#   - Initial Release (v1.0)
54#
55#  2007-07-31 Dwayne C. Litzenberger <dlitz@dlitz.net>
56#   - Bugfix release (v1.1)
57#   - SECURITY: The PyCrypto XOR cipher (used, if available, in the _strxor
58#   function in the previous release) silently truncates all keys to 64
59#   bytes.  The way it was used in the previous release, this would only be
60#   problem if the pseudorandom function that returned values larger than
61#   64 bytes (so SHA1, SHA256 and SHA512 are fine), but I don't like
62#   anything that silently reduces the security margin from what is
63#   expected.
64#
65###########################################################################
66
67__version__ = "1.1"
68
69from struct import pack
70from binascii import b2a_hex
71from random import randint
72
73from beaker.util import b64encode
74
75try:
76    # Use PyCrypto (if available)
77    from Crypto.Hash import HMAC, SHA as SHA1
78
79except ImportError:
80    # PyCrypto not available.  Use the Python standard library.
81    import hmac as HMAC
82    import sys
83    # When using the stdlib, we have to make sure the hmac version and sha
84    # version are compatible
85    if sys.version_info[0:2] <= (2,4):
86        # hmac in python2.4 or less require the sha module
87        import sha as SHA1
88    else:
89        # NOTE: We have to use the callable with hashlib (hashlib.sha1),
90        # otherwise hmac only accepts the sha module object itself
91        from hashlib import sha1 as SHA1
92
93def strxor(a, b):
94    return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])
95
96class PBKDF2(object):
97    """PBKDF2.py : PKCS#5 v2.0 Password-Based Key Derivation
98   
99    This implementation takes a passphrase and a salt (and optionally an
100    iteration count, a digest module, and a MAC module) and provides a
101    file-like object from which an arbitrarily-sized key can be read.
102
103    If the passphrase and/or salt are unicode objects, they are encoded as
104    UTF-8 before they are processed.
105
106    The idea behind PBKDF2 is to derive a cryptographic key from a
107    passphrase and a salt.
108   
109    PBKDF2 may also be used as a strong salted password hash.  The
110    'crypt' function is provided for that purpose.
111   
112    Remember: Keys generated using PBKDF2 are only as strong as the
113    passphrases they are derived from.
114    """
115
116    def __init__(self, passphrase, salt, iterations=1000,
117                 digestmodule=SHA1, macmodule=HMAC):
118        if not callable(macmodule):
119            macmodule = macmodule.new
120        self.__macmodule = macmodule
121        self.__digestmodule = digestmodule
122        self._setup(passphrase, salt, iterations, self._pseudorandom)
123
124    def _pseudorandom(self, key, msg):
125        """Pseudorandom function.  e.g. HMAC-SHA1"""
126        return self.__macmodule(key=key, msg=msg,
127            digestmod=self.__digestmodule).digest()
128   
129    def read(self, bytes):
130        """Read the specified number of key bytes."""
131        if self.closed:
132            raise ValueError("file-like object is closed")
133
134        size = len(self.__buf)
135        blocks = [self.__buf]
136        i = self.__blockNum
137        while size < bytes:
138            i += 1
139            if i > 0xffffffff:
140                # We could return "" here, but
141                raise OverflowError("derived key too long")
142            block = self.__f(i)
143            blocks.append(block)
144            size += len(block)
145        buf = "".join(blocks)
146        retval = buf[:bytes]
147        self.__buf = buf[bytes:]
148        self.__blockNum = i
149        return retval
150   
151    def __f(self, i):
152        # i must fit within 32 bits
153        assert (1 <= i and i <= 0xffffffff)
154        U = self.__prf(self.__passphrase, self.__salt + pack("!L", i))
155        result = U
156        for j in xrange(2, 1+self.__iterations):
157            U = self.__prf(self.__passphrase, U)
158            result = strxor(result, U)
159        return result
160   
161    def hexread(self, octets):
162        """Read the specified number of octets. Return them as hexadecimal.
163
164        Note that len(obj.hexread(n)) == 2*n.
165        """
166        return b2a_hex(self.read(octets))
167
168    def _setup(self, passphrase, salt, iterations, prf):
169        # Sanity checks:
170       
171        # passphrase and salt must be str or unicode (in the latter
172        # case, we convert to UTF-8)
173        if isinstance(passphrase, unicode):
174            passphrase = passphrase.encode("UTF-8")
175        if not isinstance(passphrase, str):
176            raise TypeError("passphrase must be str or unicode")
177        if isinstance(salt, unicode):
178            salt = salt.encode("UTF-8")
179        if not isinstance(salt, str):
180            raise TypeError("salt must be str or unicode")
181
182        # iterations must be an integer >= 1
183        if not isinstance(iterations, (int, long)):
184            raise TypeError("iterations must be an integer")
185        if iterations < 1:
186            raise ValueError("iterations must be at least 1")
187       
188        # prf must be callable
189        if not callable(prf):
190            raise TypeError("prf must be callable")
191
192        self.__passphrase = passphrase
193        self.__salt = salt
194        self.__iterations = iterations
195        self.__prf = prf
196        self.__blockNum = 0
197        self.__buf = ""
198        self.closed = False
199   
200    def close(self):
201        """Close the stream."""
202        if not self.closed:
203            del self.__passphrase
204            del self.__salt
205            del self.__iterations
206            del self.__prf
207            del self.__blockNum
208            del self.__buf
209            self.closed = True
210
211def crypt(word, salt=None, iterations=None):
212    """PBKDF2-based unix crypt(3) replacement.
213   
214    The number of iterations specified in the salt overrides the 'iterations'
215    parameter.
216
217    The effective hash length is 192 bits.
218    """
219   
220    # Generate a (pseudo-)random salt if the user hasn't provided one.
221    if salt is None:
222        salt = _makesalt()
223
224    # salt must be a string or the us-ascii subset of unicode
225    if isinstance(salt, unicode):
226        salt = salt.encode("us-ascii")
227    if not isinstance(salt, str):
228        raise TypeError("salt must be a string")
229
230    # word must be a string or unicode (in the latter case, we convert to UTF-8)
231    if isinstance(word, unicode):
232        word = word.encode("UTF-8")
233    if not isinstance(word, str):
234        raise TypeError("word must be a string or unicode")
235
236    # Try to extract the real salt and iteration count from the salt
237    if salt.startswith("$p5k2$"):
238        (iterations, salt, dummy) = salt.split("$")[2:5]
239        if iterations == "":
240            iterations = 400
241        else:
242            converted = int(iterations, 16)
243            if iterations != "%x" % converted:  # lowercase hex, minimum digits
244                raise ValueError("Invalid salt")
245            iterations = converted
246            if not (iterations >= 1):
247                raise ValueError("Invalid salt")
248   
249    # Make sure the salt matches the allowed character set
250    allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
251    for ch in salt:
252        if ch not in allowed:
253            raise ValueError("Illegal character %r in salt" % (ch,))
254
255    if iterations is None or iterations == 400:
256        iterations = 400
257        salt = "$p5k2$$" + salt
258    else:
259        salt = "$p5k2$%x$%s" % (iterations, salt)
260    rawhash = PBKDF2(word, salt, iterations).read(24)
261    return salt + "$" + b64encode(rawhash, "./")
262
263# Add crypt as a static method of the PBKDF2 class
264# This makes it easier to do "from PBKDF2 import PBKDF2" and still use
265# crypt.
266PBKDF2.crypt = staticmethod(crypt)
267
268def _makesalt():
269    """Return a 48-bit pseudorandom salt for crypt().
270   
271    This function is not suitable for generating cryptographic secrets.
272    """
273    binarysalt = "".join([pack("@H", randint(0, 0xffff)) for i in range(3)])
274    return b64encode(binarysalt, "./")
275
276def test_pbkdf2():
277    """Module self-test"""
278    from binascii import a2b_hex
279   
280    #
281    # Test vectors from RFC 3962
282    #
283
284    # Test 1
285    result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1).read(16)
286    expected = a2b_hex("cdedb5281bb2f801565a1122b2563515")
287    if result != expected:
288        raise RuntimeError("self-test failed")
289
290    # Test 2
291    result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1200).hexread(32)
292    expected = ("5c08eb61fdf71e4e4ec3cf6ba1f5512b"
293                "a7e52ddbc5e5142f708a31e2e62b1e13")
294    if result != expected:
295        raise RuntimeError("self-test failed")
296
297    # Test 3
298    result = PBKDF2("X"*64, "pass phrase equals block size", 1200).hexread(32)
299    expected = ("139c30c0966bc32ba55fdbf212530ac9"
300                "c5ec59f1a452f5cc9ad940fea0598ed1")
301    if result != expected:
302        raise RuntimeError("self-test failed")
303   
304    # Test 4
305    result = PBKDF2("X"*65, "pass phrase exceeds block size", 1200).hexread(32)
306    expected = ("9ccad6d468770cd51b10e6a68721be61"
307                "1a8b4d282601db3b36be9246915ec82a")
308    if result != expected:
309        raise RuntimeError("self-test failed")
310   
311    #
312    # Other test vectors
313    #
314   
315    # Chunked read
316    f = PBKDF2("kickstart", "workbench", 256)
317    result = f.read(17)
318    result += f.read(17)
319    result += f.read(1)
320    result += f.read(2)
321    result += f.read(3)
322    expected = PBKDF2("kickstart", "workbench", 256).read(40)
323    if result != expected:
324        raise RuntimeError("self-test failed")
325   
326    #
327    # crypt() test vectors
328    #
329
330    # crypt 1
331    result = crypt("cloadm", "exec")
332    expected = '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql'
333    if result != expected:
334        raise RuntimeError("self-test failed")
335   
336    # crypt 2
337    result = crypt("gnu", '$p5k2$c$u9HvcT4d$.....')
338    expected = '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g'
339    if result != expected:
340        raise RuntimeError("self-test failed")
341
342    # crypt 3
343    result = crypt("dcl", "tUsch7fU", iterations=13)
344    expected = "$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL"
345    if result != expected:
346        raise RuntimeError("self-test failed")
347   
348    # crypt 4 (unicode)
349    result = crypt(u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2',
350        '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ')
351    expected = '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ'
352    if result != expected:
353        raise RuntimeError("self-test failed")
354
355if __name__ == '__main__':
356    test_pbkdf2()
357
358# vim:set ts=4 sw=4 sts=4 expandtab:
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。