~ubuntu-branches/ubuntu/wily/python-pbkdf2/wily

« back to all changes in this revision

Viewing changes to pbkdf2.py

  • Committer: Package Import Robot
  • Author(s): Alessio Treglia
  • Date: 2013-01-12 21:34:30 UTC
  • Revision ID: package-import@ubuntu.com-20130112213430-pmn4bxfhuv920lns
Tags: upstream-1.3+20110613.git2a0fb15~ds0
ImportĀ upstreamĀ versionĀ 1.3+20110613.git2a0fb15~ds0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
# -*- coding: ascii -*-
 
3
###########################################################################
 
4
# pbkdf2 - PKCS#5 v2.0 Password-Based Key Derivation
 
5
#
 
6
# Copyright (C) 2007-2011 Dwayne C. Litzenberger <dlitz@dlitz.net>
 
7
#
 
8
# Permission is hereby granted, free of charge, to any person obtaining
 
9
# a copy of this software and associated documentation files (the
 
10
# "Software"), to deal in the Software without restriction, including
 
11
# without limitation the rights to use, copy, modify, merge, publish,
 
12
# distribute, sublicense, and/or sell copies of the Software, and to
 
13
# permit persons to whom the Software is furnished to do so, subject to
 
14
# the following conditions:
 
15
#
 
16
# The above copyright notice and this permission notice shall be
 
17
# included in all copies or substantial portions of the Software.
 
18
#
 
19
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 
20
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 
21
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 
22
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 
23
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 
24
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 
25
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
26
#
 
27
# Country of origin: Canada
 
28
#
 
29
###########################################################################
 
30
# Sample PBKDF2 usage:
 
31
#   from Crypto.Cipher import AES
 
32
#   from pbkdf2 import PBKDF2
 
33
#   import os
 
34
#
 
35
#   salt = os.urandom(8)    # 64-bit salt
 
36
#   key = PBKDF2("This passphrase is a secret.", salt).read(32) # 256-bit key
 
37
#   iv = os.urandom(16)     # 128-bit IV
 
38
#   cipher = AES.new(key, AES.MODE_CBC, iv)
 
39
#     ...
 
40
#
 
41
# Sample crypt() usage:
 
42
#   from pbkdf2 import crypt
 
43
#   pwhash = crypt("secret")
 
44
#   alleged_pw = raw_input("Enter password: ")
 
45
#   if pwhash == crypt(alleged_pw, pwhash):
 
46
#       print "Password good"
 
47
#   else:
 
48
#       print "Invalid password"
 
49
#
 
50
###########################################################################
 
51
 
 
52
__version__ = "1.3"
 
53
__all__ = ['PBKDF2', 'crypt']
 
54
 
 
55
from struct import pack
 
56
from random import randint
 
57
import string
 
58
import sys
 
59
 
 
60
try:
 
61
    # Use PyCrypto (if available).
 
62
    from Crypto.Hash import HMAC, SHA as SHA1
 
63
except ImportError:
 
64
    # PyCrypto not available.  Use the Python standard library.
 
65
    import hmac as HMAC
 
66
    try:
 
67
        from hashlib import sha1 as SHA1
 
68
    except ImportError:
 
69
        # hashlib not available.  Use the old sha module.
 
70
        import sha as SHA1
 
71
 
 
72
#
 
73
# Python 2.1 thru 3.2 compatibility
 
74
#
 
75
 
 
76
if sys.version_info[0] == 2:
 
77
    _0xffffffffL = long(1) << 32
 
78
    def isunicode(s):
 
79
        return isinstance(s, unicode)
 
80
    def isbytes(s):
 
81
        return isinstance(s, str)
 
82
    def isinteger(n):
 
83
        return isinstance(n, (int, long))
 
84
    def b(s):
 
85
        return s
 
86
    def binxor(a, b):
 
87
        return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])
 
88
    def b64encode(data, chars="+/"):
 
89
        tt = string.maketrans("+/", chars)
 
90
        return data.encode('base64').replace("\n", "").translate(tt)
 
91
    from binascii import b2a_hex
 
92
else:
 
93
    _0xffffffffL = 0xffffffff
 
94
    def isunicode(s):
 
95
        return isinstance(s, str)
 
96
    def isbytes(s):
 
97
        return isinstance(s, bytes)
 
98
    def isinteger(n):
 
99
        return isinstance(n, int)
 
100
    def callable(obj):
 
101
        return hasattr(obj, '__call__')
 
102
    def b(s):
 
103
       return s.encode("latin-1")
 
104
    def binxor(a, b):
 
105
        return bytes([x ^ y for (x, y) in zip(a, b)])
 
106
    from base64 import b64encode as _b64encode
 
107
    def b64encode(data, chars="+/"):
 
108
        if isunicode(chars):
 
109
            return _b64encode(data, chars.encode('utf-8')).decode('utf-8')
 
110
        else:
 
111
            return _b64encode(data, chars)
 
112
    from binascii import b2a_hex as _b2a_hex
 
113
    def b2a_hex(s):
 
114
        return _b2a_hex(s).decode('us-ascii')
 
115
    xrange = range
 
116
 
 
117
class PBKDF2(object):
 
118
    """PBKDF2.py : PKCS#5 v2.0 Password-Based Key Derivation
 
119
 
 
120
    This implementation takes a passphrase and a salt (and optionally an
 
121
    iteration count, a digest module, and a MAC module) and provides a
 
122
    file-like object from which an arbitrarily-sized key can be read.
 
123
 
 
124
    If the passphrase and/or salt are unicode objects, they are encoded as
 
125
    UTF-8 before they are processed.
 
126
 
 
127
    The idea behind PBKDF2 is to derive a cryptographic key from a
 
128
    passphrase and a salt.
 
129
 
 
130
    PBKDF2 may also be used as a strong salted password hash.  The
 
131
    'crypt' function is provided for that purpose.
 
132
 
 
133
    Remember: Keys generated using PBKDF2 are only as strong as the
 
134
    passphrases they are derived from.
 
135
    """
 
136
 
 
137
    def __init__(self, passphrase, salt, iterations=1000,
 
138
                 digestmodule=SHA1, macmodule=HMAC):
 
139
        self.__macmodule = macmodule
 
140
        self.__digestmodule = digestmodule
 
141
        self._setup(passphrase, salt, iterations, self._pseudorandom)
 
142
 
 
143
    def _pseudorandom(self, key, msg):
 
144
        """Pseudorandom function.  e.g. HMAC-SHA1"""
 
145
        return self.__macmodule.new(key=key, msg=msg,
 
146
            digestmod=self.__digestmodule).digest()
 
147
 
 
148
    def read(self, bytes):
 
149
        """Read the specified number of key bytes."""
 
150
        if self.closed:
 
151
            raise ValueError("file-like object is closed")
 
152
 
 
153
        size = len(self.__buf)
 
154
        blocks = [self.__buf]
 
155
        i = self.__blockNum
 
156
        while size < bytes:
 
157
            i += 1
 
158
            if i > _0xffffffffL or i < 1:
 
159
                # We could return "" here, but
 
160
                raise OverflowError("derived key too long")
 
161
            block = self.__f(i)
 
162
            blocks.append(block)
 
163
            size += len(block)
 
164
        buf = b("").join(blocks)
 
165
        retval = buf[:bytes]
 
166
        self.__buf = buf[bytes:]
 
167
        self.__blockNum = i
 
168
        return retval
 
169
 
 
170
    def __f(self, i):
 
171
        # i must fit within 32 bits
 
172
        assert 1 <= i <= _0xffffffffL
 
173
        U = self.__prf(self.__passphrase, self.__salt + pack("!L", i))
 
174
        result = U
 
175
        for j in xrange(2, 1+self.__iterations):
 
176
            U = self.__prf(self.__passphrase, U)
 
177
            result = binxor(result, U)
 
178
        return result
 
179
 
 
180
    def hexread(self, octets):
 
181
        """Read the specified number of octets. Return them as hexadecimal.
 
182
 
 
183
        Note that len(obj.hexread(n)) == 2*n.
 
184
        """
 
185
        return b2a_hex(self.read(octets))
 
186
 
 
187
    def _setup(self, passphrase, salt, iterations, prf):
 
188
        # Sanity checks:
 
189
 
 
190
        # passphrase and salt must be str or unicode (in the latter
 
191
        # case, we convert to UTF-8)
 
192
        if isunicode(passphrase):
 
193
            passphrase = passphrase.encode("UTF-8")
 
194
        elif not isbytes(passphrase):
 
195
            raise TypeError("passphrase must be str or unicode")
 
196
        if isunicode(salt):
 
197
            salt = salt.encode("UTF-8")
 
198
        elif not isbytes(salt):
 
199
            raise TypeError("salt must be str or unicode")
 
200
 
 
201
        # iterations must be an integer >= 1
 
202
        if not isinteger(iterations):
 
203
            raise TypeError("iterations must be an integer")
 
204
        if iterations < 1:
 
205
            raise ValueError("iterations must be at least 1")
 
206
 
 
207
        # prf must be callable
 
208
        if not callable(prf):
 
209
            raise TypeError("prf must be callable")
 
210
 
 
211
        self.__passphrase = passphrase
 
212
        self.__salt = salt
 
213
        self.__iterations = iterations
 
214
        self.__prf = prf
 
215
        self.__blockNum = 0
 
216
        self.__buf = b("")
 
217
        self.closed = False
 
218
 
 
219
    def close(self):
 
220
        """Close the stream."""
 
221
        if not self.closed:
 
222
            del self.__passphrase
 
223
            del self.__salt
 
224
            del self.__iterations
 
225
            del self.__prf
 
226
            del self.__blockNum
 
227
            del self.__buf
 
228
            self.closed = True
 
229
 
 
230
def crypt(word, salt=None, iterations=None):
 
231
    """PBKDF2-based unix crypt(3) replacement.
 
232
 
 
233
    The number of iterations specified in the salt overrides the 'iterations'
 
234
    parameter.
 
235
 
 
236
    The effective hash length is 192 bits.
 
237
    """
 
238
 
 
239
    # Generate a (pseudo-)random salt if the user hasn't provided one.
 
240
    if salt is None:
 
241
        salt = _makesalt()
 
242
 
 
243
    # salt must be a string or the us-ascii subset of unicode
 
244
    if isunicode(salt):
 
245
        salt = salt.encode('us-ascii').decode('us-ascii')
 
246
    elif isbytes(salt):
 
247
        salt = salt.decode('us-ascii')
 
248
    else:
 
249
        raise TypeError("salt must be a string")
 
250
 
 
251
    # word must be a string or unicode (in the latter case, we convert to UTF-8)
 
252
    if isunicode(word):
 
253
        word = word.encode("UTF-8")
 
254
    elif not isbytes(word):
 
255
        raise TypeError("word must be a string or unicode")
 
256
 
 
257
    # Try to extract the real salt and iteration count from the salt
 
258
    if salt.startswith("$p5k2$"):
 
259
        (iterations, salt, dummy) = salt.split("$")[2:5]
 
260
        if iterations == "":
 
261
            iterations = 400
 
262
        else:
 
263
            converted = int(iterations, 16)
 
264
            if iterations != "%x" % converted:  # lowercase hex, minimum digits
 
265
                raise ValueError("Invalid salt")
 
266
            iterations = converted
 
267
            if not (iterations >= 1):
 
268
                raise ValueError("Invalid salt")
 
269
 
 
270
    # Make sure the salt matches the allowed character set
 
271
    allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
 
272
    for ch in salt:
 
273
        if ch not in allowed:
 
274
            raise ValueError("Illegal character %r in salt" % (ch,))
 
275
 
 
276
    if iterations is None or iterations == 400:
 
277
        iterations = 400
 
278
        salt = "$p5k2$$" + salt
 
279
    else:
 
280
        salt = "$p5k2$%x$%s" % (iterations, salt)
 
281
    rawhash = PBKDF2(word, salt, iterations).read(24)
 
282
    return salt + "$" + b64encode(rawhash, "./")
 
283
 
 
284
# Add crypt as a static method of the PBKDF2 class
 
285
# This makes it easier to do "from PBKDF2 import PBKDF2" and still use
 
286
# crypt.
 
287
PBKDF2.crypt = staticmethod(crypt)
 
288
 
 
289
def _makesalt():
 
290
    """Return a 48-bit pseudorandom salt for crypt().
 
291
 
 
292
    This function is not suitable for generating cryptographic secrets.
 
293
    """
 
294
    binarysalt = b("").join([pack("@H", randint(0, 0xffff)) for i in range(3)])
 
295
    return b64encode(binarysalt, "./")
 
296
 
 
297
# vim:set ts=4 sw=4 sts=4 expandtab: