├── .gitignore ├── README.txt ├── pbkdf2.py ├── setup.py └── test ├── __init__.py └── test_pbkdf2.py /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /dist 3 | /*.egg-info 4 | *.pyc 5 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | Python PKCS#5 v2.0 PBKDF2 Module 2 | -------------------------------- 3 | 4 | This module implements the password-based key derivation function, PBKDF2, 5 | specified in `RSA PKCS#5 v2.0 `_. 6 | 7 | Example PBKDF2 usage 8 | ==================== 9 | 10 | :: 11 | 12 | from pbkdf2 import PBKDF2 13 | from Crypto.Cipher import AES 14 | import os 15 | 16 | salt = os.urandom(8) # 64-bit salt 17 | key = PBKDF2("This passphrase is a secret.", salt).read(32) # 256-bit key 18 | iv = os.urandom(16) # 128-bit IV 19 | cipher = AES.new(key, AES.MODE_CBC, iv) 20 | # ... 21 | 22 | Example crypt() usage 23 | ===================== 24 | 25 | :: 26 | 27 | from pbkdf2 import crypt 28 | pwhash = crypt("secret") 29 | alleged_pw = raw_input("Enter password: ") 30 | if pwhash == crypt(alleged_pw, pwhash): 31 | print "Password good" 32 | else: 33 | print "Invalid password" 34 | 35 | Example crypt() output 36 | ====================== 37 | 38 | :: 39 | 40 | >>> from pbkdf2 import crypt 41 | >>> crypt("secret") 42 | '$p5k2$$hi46RA73$aGBpfPOgOrgZLaHGweSQzJ5FLz4BsQVs' 43 | >>> crypt("secret", "XXXXXXXX") 44 | '$p5k2$$XXXXXXXX$L9mVVdq7upotdvtGvXTDTez3FIu3z0uG' 45 | >>> crypt("secret", "XXXXXXXX", 400) # 400 iterations (the default for crypt) 46 | '$p5k2$$XXXXXXXX$L9mVVdq7upotdvtGvXTDTez3FIu3z0uG' 47 | >>> crypt("spam", iterations=400) 48 | '$p5k2$$FRsH3HJB$SgRWDNmB2LukCy0OTal6LYLHZVgtOi7s' 49 | >>> crypt("spam", iterations=1000) # 1000 iterations 50 | '$p5k2$3e8$H0NX9mT/$wk/sE8vv6OMKuMaqazCJYDSUhWY9YB2J' 51 | 52 | 53 | Resources 54 | ========= 55 | 56 | Homepage 57 | https://www.dlitz.net/software/python-pbkdf2/ 58 | 59 | Source Code 60 | https://github.com/dlitz/python-pbkdf2/ 61 | 62 | PyPI package name 63 | `pbkdf2 `_ 64 | 65 | License 66 | ======= 67 | Copyright (C) 2007-2011 Dwayne C. Litzenberger 68 | 69 | Permission is hereby granted, free of charge, to any person obtaining 70 | a copy of this software and associated documentation files (the 71 | "Software"), to deal in the Software without restriction, including 72 | without limitation the rights to use, copy, modify, merge, publish, 73 | distribute, sublicense, and/or sell copies of the Software, and to 74 | permit persons to whom the Software is furnished to do so, subject to 75 | the following conditions: 76 | 77 | The above copyright notice and this permission notice shall be 78 | included in all copies or substantial portions of the Software. 79 | 80 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 81 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 82 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 83 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 84 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 85 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 86 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 87 | -------------------------------------------------------------------------------- /pbkdf2.py: -------------------------------------------------------------------------------- 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 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: 298 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Use setuptools, if available. Otherwise, fall back to distutils. 4 | try: 5 | from setuptools import setup 6 | except ImportError: 7 | import sys 8 | sys.stderr.write("warning: Proceeding without setuptools\n") 9 | from distutils.core import setup 10 | 11 | from pbkdf2 import __version__ 12 | 13 | setup( 14 | name='pbkdf2', 15 | py_modules=['pbkdf2'], 16 | version=__version__, 17 | test_suite='test', 18 | description='PKCS#5 v2.0 PBKDF2 Module', 19 | author='Dwayne C. Litzenberger', 20 | author_email='dlitz@dlitz.net', 21 | url='http://www.dlitz.net/software/python-pbkdf2/', 22 | classifiers=[ 23 | 'Development Status :: 5 - Production/Stable', 24 | 'Intended Audience :: Developers', 25 | 'License :: OSI Approved :: MIT License', 26 | 'Operating System :: OS Independent', 27 | 'Programming Language :: Python', 28 | 'Programming Language :: Python :: 2', 29 | 'Programming Language :: Python :: 3', 30 | 'Topic :: Security :: Cryptography', 31 | ], 32 | long_description="""\ 33 | This module implements the password-based key derivation function, PBKDF2, specified in RSA PKCS#5 v2.0. 34 | """) 35 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | import test.test_pbkdf2 2 | -------------------------------------------------------------------------------- /test/test_pbkdf2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: ascii -*- 3 | 4 | import unittest 5 | from pbkdf2 import PBKDF2, crypt, b 6 | 7 | import sys 8 | 9 | class TestPBKDF2(unittest.TestCase): 10 | def test_pbkdf2(self): 11 | """Module self-test""" 12 | from binascii import a2b_hex as _a2b_hex 13 | def a2b_hex(s): 14 | return _a2b_hex(b(s)) 15 | 16 | # 17 | # Test vectors from RFC 3962 18 | # 19 | 20 | # Test 1 21 | result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1).read(16) 22 | expected = a2b_hex("cdedb5281bb2f801565a1122b2563515") 23 | self.assertEqual(expected, result) 24 | 25 | # Test 2 26 | result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1200).hexread(32) 27 | expected = ("5c08eb61fdf71e4e4ec3cf6ba1f5512b" 28 | "a7e52ddbc5e5142f708a31e2e62b1e13") 29 | self.assertEqual(expected, result) 30 | 31 | # Test 3 32 | result = PBKDF2("X"*64, "pass phrase equals block size", 1200).hexread(32) 33 | expected = ("139c30c0966bc32ba55fdbf212530ac9" 34 | "c5ec59f1a452f5cc9ad940fea0598ed1") 35 | self.assertEqual(expected, result) 36 | 37 | # Test 4 38 | result = PBKDF2("X"*65, "pass phrase exceeds block size", 1200).hexread(32) 39 | expected = ("9ccad6d468770cd51b10e6a68721be61" 40 | "1a8b4d282601db3b36be9246915ec82a") 41 | self.assertEqual(expected, result) 42 | 43 | # 44 | # Other test vectors 45 | # 46 | 47 | # Chunked read 48 | f = PBKDF2("kickstart", "workbench", 256) 49 | result = f.read(17) 50 | result += f.read(17) 51 | result += f.read(1) 52 | result += f.read(2) 53 | result += f.read(3) 54 | expected = PBKDF2("kickstart", "workbench", 256).read(40) 55 | self.assertEqual(expected, result) 56 | 57 | # 58 | # crypt() test vectors 59 | # 60 | 61 | # crypt 1 62 | result = crypt("cloadm", "exec") 63 | expected = '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql' 64 | self.assertEqual(expected, result) 65 | 66 | # crypt 2 67 | result = crypt("gnu", '$p5k2$c$u9HvcT4d$.....') 68 | expected = '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g' 69 | self.assertEqual(expected, result) 70 | 71 | # crypt 3 72 | result = crypt("dcl", "tUsch7fU", iterations=13) 73 | expected = "$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL" 74 | self.assertEqual(expected, result) 75 | 76 | # crypt 4 (unicode) 77 | result = crypt(b('\xce\x99\xcf\x89\xce\xb1\xce\xbd\xce\xbd\xce\xb7\xcf\x82').decode('utf-8'), 78 | '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ') 79 | expected = '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ' 80 | self.assertEqual(expected, result) 81 | 82 | # crypt 5 (UTF-8 bytes) 83 | result = crypt(b('\xce\x99\xcf\x89\xce\xb1\xce\xbd\xce\xbd\xce\xb7\xcf\x82'), 84 | '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ') 85 | expected = '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ' 86 | self.assertEqual(expected, result) 87 | 88 | def test_crypt(self): 89 | result = crypt("secret") 90 | self.assertEqual(result[:6], "$p5k2$") 91 | 92 | result = crypt("secret", "XXXXXXXX") 93 | expected = '$p5k2$$XXXXXXXX$L9mVVdq7upotdvtGvXTDTez3FIu3z0uG' 94 | self.assertEqual(expected, result) 95 | 96 | # 400 iterations (the default for crypt) 97 | result = crypt("secret", "XXXXXXXX", 400) 98 | expected = '$p5k2$$XXXXXXXX$L9mVVdq7upotdvtGvXTDTez3FIu3z0uG' 99 | self.assertEqual(expected, result) 100 | 101 | # 400 iterations (keyword argument) 102 | result = crypt("spam", "FRsH3HJB", iterations=400) 103 | expected = '$p5k2$$FRsH3HJB$SgRWDNmB2LukCy0OTal6LYLHZVgtOi7s' 104 | self.assertEqual(expected, result) 105 | 106 | # 1000 iterations 107 | result = crypt("spam", "H0NX9mT/", iterations=1000) 108 | expected = '$p5k2$3e8$H0NX9mT/$wk/sE8vv6OMKuMaqazCJYDSUhWY9YB2J' 109 | self.assertEqual(expected, result) 110 | 111 | # 1000 iterations (iterations count taken from salt parameter) 112 | expected = '$p5k2$3e8$H0NX9mT/$wk/sE8vv6OMKuMaqazCJYDSUhWY9YB2J' 113 | result = crypt("spam", expected) 114 | self.assertEqual(expected, result) 115 | 116 | if __name__ == '__main__': 117 | unittest.main() 118 | 119 | # vim:set ts=4 sw=4 sts=4 expandtab: 120 | --------------------------------------------------------------------------------