OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/python |
| 2 # -*- coding: ascii -*- |
| 3 ########################################################################### |
| 4 # PBKDF2.py - PKCS#5 v2.0 Password-Based Key Derivation |
| 5 # |
| 6 # Copyright (C) 2007, 2008 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 # 2008-06-17 Dwayne C. Litzenberger <dlitz@dlitz.net> |
| 66 # - Compatibility release (v1.2) |
| 67 # - Add support for older versions of Python (2.2 and 2.3). |
| 68 # |
| 69 ########################################################################### |
| 70 |
| 71 __version__ = "1.2" |
| 72 |
| 73 from builtins import chr |
| 74 from builtins import zip |
| 75 from builtins import range |
| 76 from builtins import object |
| 77 |
| 78 import string |
| 79 from struct import pack |
| 80 from binascii import b2a_hex |
| 81 from random import randint |
| 82 |
| 83 try: |
| 84 # Use PyCrypto (if available) |
| 85 from Crypto.Hash import HMAC, SHA as SHA1 |
| 86 |
| 87 except ImportError: |
| 88 # PyCrypto not available. Use the Python standard library. |
| 89 import hmac as HMAC |
| 90 import sha as SHA1 |
| 91 |
| 92 def strxor(a, b): |
| 93 return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)]) |
| 94 |
| 95 def b64encode(data, chars="+/"): |
| 96 tt = string.maketrans("+/", chars) |
| 97 return data.encode('base64').replace("\n", "").translate(tt) |
| 98 |
| 99 class PBKDF2(object): |
| 100 """PBKDF2.py : PKCS#5 v2.0 Password-Based Key Derivation |
| 101 |
| 102 This implementation takes a passphrase and a salt (and optionally an |
| 103 iteration count, a digest module, and a MAC module) and provides a |
| 104 file-like object from which an arbitrarily-sized key can be read. |
| 105 |
| 106 If the passphrase and/or salt are unicode objects, they are encoded as |
| 107 UTF-8 before they are processed. |
| 108 |
| 109 The idea behind PBKDF2 is to derive a cryptographic key from a |
| 110 passphrase and a salt. |
| 111 |
| 112 PBKDF2 may also be used as a strong salted password hash. The |
| 113 'crypt' function is provided for that purpose. |
| 114 |
| 115 Remember: Keys generated using PBKDF2 are only as strong as the |
| 116 passphrases they are derived from. |
| 117 """ |
| 118 |
| 119 def __init__(self, passphrase, salt, iterations=1000, |
| 120 digestmodule=SHA1, macmodule=HMAC): |
| 121 self.__macmodule = macmodule |
| 122 self.__digestmodule = digestmodule |
| 123 self._setup(passphrase, salt, iterations, self._pseudorandom) |
| 124 |
| 125 def _pseudorandom(self, key, msg): |
| 126 """Pseudorandom function. e.g. HMAC-SHA1""" |
| 127 return self.__macmodule.new(key=key, msg=msg, |
| 128 digestmod=self.__digestmodule).digest() |
| 129 |
| 130 def read(self, bytes): |
| 131 """Read the specified number of key bytes.""" |
| 132 if self.closed: |
| 133 raise ValueError("file-like object is closed") |
| 134 |
| 135 size = len(self.__buf) |
| 136 blocks = [self.__buf] |
| 137 i = self.__blockNum |
| 138 while size < bytes: |
| 139 i += 1 |
| 140 if i > 0xffffffff or i < 1: |
| 141 # We could return "" here, but |
| 142 raise OverflowError("derived key too long") |
| 143 block = self.__f(i) |
| 144 blocks.append(block) |
| 145 size += len(block) |
| 146 buf = "".join(blocks) |
| 147 retval = buf[:bytes] |
| 148 self.__buf = buf[bytes:] |
| 149 self.__blockNum = i |
| 150 return retval |
| 151 |
| 152 def __f(self, i): |
| 153 # i must fit within 32 bits |
| 154 assert 1 <= i <= 0xffffffff |
| 155 U = self.__prf(self.__passphrase, self.__salt + pack("!L", i)) |
| 156 result = U |
| 157 for j in range(2, 1+self.__iterations): |
| 158 U = self.__prf(self.__passphrase, U) |
| 159 result = strxor(result, U) |
| 160 return result |
| 161 |
| 162 def hexread(self, octets): |
| 163 """Read the specified number of octets. Return them as hexadecimal. |
| 164 |
| 165 Note that len(obj.hexread(n)) == 2*n. |
| 166 """ |
| 167 return b2a_hex(self.read(octets)) |
| 168 |
| 169 def _setup(self, passphrase, salt, iterations, prf): |
| 170 # Sanity checks: |
| 171 |
| 172 # passphrase and salt must be str or unicode (in the latter |
| 173 # case, we convert to UTF-8) |
| 174 if isinstance(passphrase, str): |
| 175 passphrase = passphrase.encode("UTF-8") |
| 176 if not isinstance(passphrase, str): |
| 177 raise TypeError("passphrase must be str or unicode") |
| 178 if isinstance(salt, str): |
| 179 salt = salt.encode("UTF-8") |
| 180 if not isinstance(salt, str): |
| 181 raise TypeError("salt must be str or unicode") |
| 182 |
| 183 # iterations must be an integer >= 1 |
| 184 if not isinstance(iterations, (int, int)): |
| 185 raise TypeError("iterations must be an integer") |
| 186 if iterations < 1: |
| 187 raise ValueError("iterations must be at least 1") |
| 188 |
| 189 # prf must be callable |
| 190 if not callable(prf): |
| 191 raise TypeError("prf must be callable") |
| 192 |
| 193 self.__passphrase = passphrase |
| 194 self.__salt = salt |
| 195 self.__iterations = iterations |
| 196 self.__prf = prf |
| 197 self.__blockNum = 0 |
| 198 self.__buf = "" |
| 199 self.closed = False |
| 200 |
| 201 def close(self): |
| 202 """Close the stream.""" |
| 203 if not self.closed: |
| 204 del self.__passphrase |
| 205 del self.__salt |
| 206 del self.__iterations |
| 207 del self.__prf |
| 208 del self.__blockNum |
| 209 del self.__buf |
| 210 self.closed = True |
| 211 |
| 212 def crypt(word, salt=None, iterations=None): |
| 213 """PBKDF2-based unix crypt(3) replacement. |
| 214 |
| 215 The number of iterations specified in the salt overrides the 'iterations' |
| 216 parameter. |
| 217 |
| 218 The effective hash length is 192 bits. |
| 219 """ |
| 220 |
| 221 # Generate a (pseudo-)random salt if the user hasn't provided one. |
| 222 if salt is None: |
| 223 salt = _makesalt() |
| 224 |
| 225 # salt must be a string or the us-ascii subset of unicode |
| 226 if isinstance(salt, str): |
| 227 salt = salt.encode("us-ascii") |
| 228 if not isinstance(salt, str): |
| 229 raise TypeError("salt must be a string") |
| 230 |
| 231 # word must be a string or unicode (in the latter case, we convert to UTF-8) |
| 232 if isinstance(word, str): |
| 233 word = word.encode("UTF-8") |
| 234 if not isinstance(word, str): |
| 235 raise TypeError("word must be a string or unicode") |
| 236 |
| 237 # Try to extract the real salt and iteration count from the salt |
| 238 if salt.startswith("$p5k2$"): |
| 239 (iterations, salt, dummy) = salt.split("$")[2:5] |
| 240 if iterations == "": |
| 241 iterations = 400 |
| 242 else: |
| 243 converted = int(iterations, 16) |
| 244 if iterations != "%x" % converted: # lowercase hex, minimum digits |
| 245 raise ValueError("Invalid salt") |
| 246 iterations = converted |
| 247 if not (iterations >= 1): |
| 248 raise ValueError("Invalid salt") |
| 249 |
| 250 # Make sure the salt matches the allowed character set |
| 251 allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./" |
| 252 for ch in salt: |
| 253 if ch not in allowed: |
| 254 raise ValueError("Illegal character %r in salt" % (ch,)) |
| 255 |
| 256 if iterations is None or iterations == 400: |
| 257 iterations = 400 |
| 258 salt = "$p5k2$$" + salt |
| 259 else: |
| 260 salt = "$p5k2$%x$%s" % (iterations, salt) |
| 261 rawhash = PBKDF2(word, salt, iterations).read(24) |
| 262 return salt + "$" + b64encode(rawhash, "./") |
| 263 |
| 264 # Add crypt as a static method of the PBKDF2 class |
| 265 # This makes it easier to do "from PBKDF2 import PBKDF2" and still use |
| 266 # crypt. |
| 267 PBKDF2.crypt = staticmethod(crypt) |
| 268 |
| 269 def _makesalt(): |
| 270 """Return a 48-bit pseudorandom salt for crypt(). |
| 271 |
| 272 This function is not suitable for generating cryptographic secrets. |
| 273 """ |
| 274 binarysalt = "".join([pack("@H", randint(0, 0xffff)) for i in range(3)]) |
| 275 return b64encode(binarysalt, "./") |
| 276 |
| 277 def test_pbkdf2(): |
| 278 """Module self-test""" |
| 279 from binascii import a2b_hex |
| 280 |
| 281 # |
| 282 # Test vectors from RFC 3962 |
| 283 # |
| 284 |
| 285 # Test 1 |
| 286 result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1).read(16) |
| 287 expected = a2b_hex("cdedb5281bb2f801565a1122b2563515") |
| 288 if result != expected: |
| 289 raise RuntimeError("self-test failed") |
| 290 |
| 291 # Test 2 |
| 292 result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1200).hexread(32) |
| 293 expected = ("5c08eb61fdf71e4e4ec3cf6ba1f5512b" |
| 294 "a7e52ddbc5e5142f708a31e2e62b1e13") |
| 295 if result != expected: |
| 296 raise RuntimeError("self-test failed") |
| 297 |
| 298 # Test 3 |
| 299 result = PBKDF2("X"*64, "pass phrase equals block size", 1200).hexread(32) |
| 300 expected = ("139c30c0966bc32ba55fdbf212530ac9" |
| 301 "c5ec59f1a452f5cc9ad940fea0598ed1") |
| 302 if result != expected: |
| 303 raise RuntimeError("self-test failed") |
| 304 |
| 305 # Test 4 |
| 306 result = PBKDF2("X"*65, "pass phrase exceeds block size", 1200).hexread(32) |
| 307 expected = ("9ccad6d468770cd51b10e6a68721be61" |
| 308 "1a8b4d282601db3b36be9246915ec82a") |
| 309 if result != expected: |
| 310 raise RuntimeError("self-test failed") |
| 311 |
| 312 # |
| 313 # Other test vectors |
| 314 # |
| 315 |
| 316 # Chunked read |
| 317 f = PBKDF2("kickstart", "workbench", 256) |
| 318 result = f.read(17) |
| 319 result += f.read(17) |
| 320 result += f.read(1) |
| 321 result += f.read(2) |
| 322 result += f.read(3) |
| 323 expected = PBKDF2("kickstart", "workbench", 256).read(40) |
| 324 if result != expected: |
| 325 raise RuntimeError("self-test failed") |
| 326 |
| 327 # |
| 328 # crypt() test vectors |
| 329 # |
| 330 |
| 331 # crypt 1 |
| 332 result = crypt("cloadm", "exec") |
| 333 expected = '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql' |
| 334 if result != expected: |
| 335 raise RuntimeError("self-test failed") |
| 336 |
| 337 # crypt 2 |
| 338 result = crypt("gnu", '$p5k2$c$u9HvcT4d$.....') |
| 339 expected = '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g' |
| 340 if result != expected: |
| 341 raise RuntimeError("self-test failed") |
| 342 |
| 343 # crypt 3 |
| 344 result = crypt("dcl", "tUsch7fU", iterations=13) |
| 345 expected = "$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL" |
| 346 if result != expected: |
| 347 raise RuntimeError("self-test failed") |
| 348 |
| 349 # crypt 4 (unicode) |
| 350 result = crypt(u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2', |
| 351 '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ') |
| 352 expected = '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ' |
| 353 if result != expected: |
| 354 raise RuntimeError("self-test failed") |
| 355 |
| 356 if __name__ == '__main__': |
| 357 test_pbkdf2() |
| 358 |
| 359 # vim:set ts=4 sw=4 sts=4 expandtab: |
OLD | NEW |