[3] | 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 | |
---|
| 69 | from struct import pack |
---|
| 70 | from binascii import b2a_hex |
---|
| 71 | from random import randint |
---|
| 72 | |
---|
| 73 | from beaker.util import b64encode |
---|
| 74 | |
---|
| 75 | try: |
---|
| 76 | # Use PyCrypto (if available) |
---|
| 77 | from Crypto.Hash import HMAC, SHA as SHA1 |
---|
| 78 | |
---|
| 79 | except 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 | |
---|
| 93 | def strxor(a, b): |
---|
| 94 | return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)]) |
---|
| 95 | |
---|
| 96 | class 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 | |
---|
| 211 | def 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. |
---|
| 266 | PBKDF2.crypt = staticmethod(crypt) |
---|
| 267 | |
---|
| 268 | def _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 | |
---|
| 276 | def 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 | |
---|
| 355 | if __name__ == '__main__': |
---|
| 356 | test_pbkdf2() |
---|
| 357 | |
---|
| 358 | # vim:set ts=4 sw=4 sts=4 expandtab: |
---|