| Index: third_party/google-endpoints/jwkest/PBKDF2.py
|
| diff --git a/third_party/google-endpoints/jwkest/PBKDF2.py b/third_party/google-endpoints/jwkest/PBKDF2.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..46ea678ba5f26c0c13e02bb3f1c60f0313d8bd3f
|
| --- /dev/null
|
| +++ b/third_party/google-endpoints/jwkest/PBKDF2.py
|
| @@ -0,0 +1,359 @@
|
| +#!/usr/bin/python
|
| +# -*- coding: ascii -*-
|
| +###########################################################################
|
| +# PBKDF2.py - PKCS#5 v2.0 Password-Based Key Derivation
|
| +#
|
| +# Copyright (C) 2007, 2008 Dwayne C. Litzenberger <dlitz@dlitz.net>
|
| +# All rights reserved.
|
| +#
|
| +# Permission to use, copy, modify, and distribute this software and its
|
| +# documentation for any purpose and without fee is hereby granted,
|
| +# provided that the above copyright notice appear in all copies and that
|
| +# both that copyright notice and this permission notice appear in
|
| +# supporting documentation.
|
| +#
|
| +# THE AUTHOR PROVIDES THIS SOFTWARE ``AS IS'' AND ANY EXPRESSED OR
|
| +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
| +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
| +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
| +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
| +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
| +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
| +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| +#
|
| +# Country of origin: Canada
|
| +#
|
| +###########################################################################
|
| +# Sample PBKDF2 usage:
|
| +# from Crypto.Cipher import AES
|
| +# from PBKDF2 import PBKDF2
|
| +# import os
|
| +#
|
| +# salt = os.urandom(8) # 64-bit salt
|
| +# key = PBKDF2("This passphrase is a secret.", salt).read(32) # 256-bit key
|
| +# iv = os.urandom(16) # 128-bit IV
|
| +# cipher = AES.new(key, AES.MODE_CBC, iv)
|
| +# ...
|
| +#
|
| +# Sample crypt() usage:
|
| +# from PBKDF2 import crypt
|
| +# pwhash = crypt("secret")
|
| +# alleged_pw = raw_input("Enter password: ")
|
| +# if pwhash == crypt(alleged_pw, pwhash):
|
| +# print "Password good"
|
| +# else:
|
| +# print "Invalid password"
|
| +#
|
| +###########################################################################
|
| +# History:
|
| +#
|
| +# 2007-07-27 Dwayne C. Litzenberger <dlitz@dlitz.net>
|
| +# - Initial Release (v1.0)
|
| +#
|
| +# 2007-07-31 Dwayne C. Litzenberger <dlitz@dlitz.net>
|
| +# - Bugfix release (v1.1)
|
| +# - SECURITY: The PyCrypto XOR cipher (used, if available, in the _strxor
|
| +# function in the previous release) silently truncates all keys to 64
|
| +# bytes. The way it was used in the previous release, this would only be
|
| +# problem if the pseudorandom function that returned values larger than
|
| +# 64 bytes (so SHA1, SHA256 and SHA512 are fine), but I don't like
|
| +# anything that silently reduces the security margin from what is
|
| +# expected.
|
| +#
|
| +# 2008-06-17 Dwayne C. Litzenberger <dlitz@dlitz.net>
|
| +# - Compatibility release (v1.2)
|
| +# - Add support for older versions of Python (2.2 and 2.3).
|
| +#
|
| +###########################################################################
|
| +
|
| +__version__ = "1.2"
|
| +
|
| +from builtins import chr
|
| +from builtins import zip
|
| +from builtins import range
|
| +from builtins import object
|
| +
|
| +import string
|
| +from struct import pack
|
| +from binascii import b2a_hex
|
| +from random import randint
|
| +
|
| +try:
|
| + # Use PyCrypto (if available)
|
| + from Crypto.Hash import HMAC, SHA as SHA1
|
| +
|
| +except ImportError:
|
| + # PyCrypto not available. Use the Python standard library.
|
| + import hmac as HMAC
|
| + import sha as SHA1
|
| +
|
| +def strxor(a, b):
|
| + return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])
|
| +
|
| +def b64encode(data, chars="+/"):
|
| + tt = string.maketrans("+/", chars)
|
| + return data.encode('base64').replace("\n", "").translate(tt)
|
| +
|
| +class PBKDF2(object):
|
| + """PBKDF2.py : PKCS#5 v2.0 Password-Based Key Derivation
|
| +
|
| + This implementation takes a passphrase and a salt (and optionally an
|
| + iteration count, a digest module, and a MAC module) and provides a
|
| + file-like object from which an arbitrarily-sized key can be read.
|
| +
|
| + If the passphrase and/or salt are unicode objects, they are encoded as
|
| + UTF-8 before they are processed.
|
| +
|
| + The idea behind PBKDF2 is to derive a cryptographic key from a
|
| + passphrase and a salt.
|
| +
|
| + PBKDF2 may also be used as a strong salted password hash. The
|
| + 'crypt' function is provided for that purpose.
|
| +
|
| + Remember: Keys generated using PBKDF2 are only as strong as the
|
| + passphrases they are derived from.
|
| + """
|
| +
|
| + def __init__(self, passphrase, salt, iterations=1000,
|
| + digestmodule=SHA1, macmodule=HMAC):
|
| + self.__macmodule = macmodule
|
| + self.__digestmodule = digestmodule
|
| + self._setup(passphrase, salt, iterations, self._pseudorandom)
|
| +
|
| + def _pseudorandom(self, key, msg):
|
| + """Pseudorandom function. e.g. HMAC-SHA1"""
|
| + return self.__macmodule.new(key=key, msg=msg,
|
| + digestmod=self.__digestmodule).digest()
|
| +
|
| + def read(self, bytes):
|
| + """Read the specified number of key bytes."""
|
| + if self.closed:
|
| + raise ValueError("file-like object is closed")
|
| +
|
| + size = len(self.__buf)
|
| + blocks = [self.__buf]
|
| + i = self.__blockNum
|
| + while size < bytes:
|
| + i += 1
|
| + if i > 0xffffffff or i < 1:
|
| + # We could return "" here, but
|
| + raise OverflowError("derived key too long")
|
| + block = self.__f(i)
|
| + blocks.append(block)
|
| + size += len(block)
|
| + buf = "".join(blocks)
|
| + retval = buf[:bytes]
|
| + self.__buf = buf[bytes:]
|
| + self.__blockNum = i
|
| + return retval
|
| +
|
| + def __f(self, i):
|
| + # i must fit within 32 bits
|
| + assert 1 <= i <= 0xffffffff
|
| + U = self.__prf(self.__passphrase, self.__salt + pack("!L", i))
|
| + result = U
|
| + for j in range(2, 1+self.__iterations):
|
| + U = self.__prf(self.__passphrase, U)
|
| + result = strxor(result, U)
|
| + return result
|
| +
|
| + def hexread(self, octets):
|
| + """Read the specified number of octets. Return them as hexadecimal.
|
| +
|
| + Note that len(obj.hexread(n)) == 2*n.
|
| + """
|
| + return b2a_hex(self.read(octets))
|
| +
|
| + def _setup(self, passphrase, salt, iterations, prf):
|
| + # Sanity checks:
|
| +
|
| + # passphrase and salt must be str or unicode (in the latter
|
| + # case, we convert to UTF-8)
|
| + if isinstance(passphrase, str):
|
| + passphrase = passphrase.encode("UTF-8")
|
| + if not isinstance(passphrase, str):
|
| + raise TypeError("passphrase must be str or unicode")
|
| + if isinstance(salt, str):
|
| + salt = salt.encode("UTF-8")
|
| + if not isinstance(salt, str):
|
| + raise TypeError("salt must be str or unicode")
|
| +
|
| + # iterations must be an integer >= 1
|
| + if not isinstance(iterations, (int, int)):
|
| + raise TypeError("iterations must be an integer")
|
| + if iterations < 1:
|
| + raise ValueError("iterations must be at least 1")
|
| +
|
| + # prf must be callable
|
| + if not callable(prf):
|
| + raise TypeError("prf must be callable")
|
| +
|
| + self.__passphrase = passphrase
|
| + self.__salt = salt
|
| + self.__iterations = iterations
|
| + self.__prf = prf
|
| + self.__blockNum = 0
|
| + self.__buf = ""
|
| + self.closed = False
|
| +
|
| + def close(self):
|
| + """Close the stream."""
|
| + if not self.closed:
|
| + del self.__passphrase
|
| + del self.__salt
|
| + del self.__iterations
|
| + del self.__prf
|
| + del self.__blockNum
|
| + del self.__buf
|
| + self.closed = True
|
| +
|
| +def crypt(word, salt=None, iterations=None):
|
| + """PBKDF2-based unix crypt(3) replacement.
|
| +
|
| + The number of iterations specified in the salt overrides the 'iterations'
|
| + parameter.
|
| +
|
| + The effective hash length is 192 bits.
|
| + """
|
| +
|
| + # Generate a (pseudo-)random salt if the user hasn't provided one.
|
| + if salt is None:
|
| + salt = _makesalt()
|
| +
|
| + # salt must be a string or the us-ascii subset of unicode
|
| + if isinstance(salt, str):
|
| + salt = salt.encode("us-ascii")
|
| + if not isinstance(salt, str):
|
| + raise TypeError("salt must be a string")
|
| +
|
| + # word must be a string or unicode (in the latter case, we convert to UTF-8)
|
| + if isinstance(word, str):
|
| + word = word.encode("UTF-8")
|
| + if not isinstance(word, str):
|
| + raise TypeError("word must be a string or unicode")
|
| +
|
| + # Try to extract the real salt and iteration count from the salt
|
| + if salt.startswith("$p5k2$"):
|
| + (iterations, salt, dummy) = salt.split("$")[2:5]
|
| + if iterations == "":
|
| + iterations = 400
|
| + else:
|
| + converted = int(iterations, 16)
|
| + if iterations != "%x" % converted: # lowercase hex, minimum digits
|
| + raise ValueError("Invalid salt")
|
| + iterations = converted
|
| + if not (iterations >= 1):
|
| + raise ValueError("Invalid salt")
|
| +
|
| + # Make sure the salt matches the allowed character set
|
| + allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
|
| + for ch in salt:
|
| + if ch not in allowed:
|
| + raise ValueError("Illegal character %r in salt" % (ch,))
|
| +
|
| + if iterations is None or iterations == 400:
|
| + iterations = 400
|
| + salt = "$p5k2$$" + salt
|
| + else:
|
| + salt = "$p5k2$%x$%s" % (iterations, salt)
|
| + rawhash = PBKDF2(word, salt, iterations).read(24)
|
| + return salt + "$" + b64encode(rawhash, "./")
|
| +
|
| +# Add crypt as a static method of the PBKDF2 class
|
| +# This makes it easier to do "from PBKDF2 import PBKDF2" and still use
|
| +# crypt.
|
| +PBKDF2.crypt = staticmethod(crypt)
|
| +
|
| +def _makesalt():
|
| + """Return a 48-bit pseudorandom salt for crypt().
|
| +
|
| + This function is not suitable for generating cryptographic secrets.
|
| + """
|
| + binarysalt = "".join([pack("@H", randint(0, 0xffff)) for i in range(3)])
|
| + return b64encode(binarysalt, "./")
|
| +
|
| +def test_pbkdf2():
|
| + """Module self-test"""
|
| + from binascii import a2b_hex
|
| +
|
| + #
|
| + # Test vectors from RFC 3962
|
| + #
|
| +
|
| + # Test 1
|
| + result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1).read(16)
|
| + expected = a2b_hex("cdedb5281bb2f801565a1122b2563515")
|
| + if result != expected:
|
| + raise RuntimeError("self-test failed")
|
| +
|
| + # Test 2
|
| + result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1200).hexread(32)
|
| + expected = ("5c08eb61fdf71e4e4ec3cf6ba1f5512b"
|
| + "a7e52ddbc5e5142f708a31e2e62b1e13")
|
| + if result != expected:
|
| + raise RuntimeError("self-test failed")
|
| +
|
| + # Test 3
|
| + result = PBKDF2("X"*64, "pass phrase equals block size", 1200).hexread(32)
|
| + expected = ("139c30c0966bc32ba55fdbf212530ac9"
|
| + "c5ec59f1a452f5cc9ad940fea0598ed1")
|
| + if result != expected:
|
| + raise RuntimeError("self-test failed")
|
| +
|
| + # Test 4
|
| + result = PBKDF2("X"*65, "pass phrase exceeds block size", 1200).hexread(32)
|
| + expected = ("9ccad6d468770cd51b10e6a68721be61"
|
| + "1a8b4d282601db3b36be9246915ec82a")
|
| + if result != expected:
|
| + raise RuntimeError("self-test failed")
|
| +
|
| + #
|
| + # Other test vectors
|
| + #
|
| +
|
| + # Chunked read
|
| + f = PBKDF2("kickstart", "workbench", 256)
|
| + result = f.read(17)
|
| + result += f.read(17)
|
| + result += f.read(1)
|
| + result += f.read(2)
|
| + result += f.read(3)
|
| + expected = PBKDF2("kickstart", "workbench", 256).read(40)
|
| + if result != expected:
|
| + raise RuntimeError("self-test failed")
|
| +
|
| + #
|
| + # crypt() test vectors
|
| + #
|
| +
|
| + # crypt 1
|
| + result = crypt("cloadm", "exec")
|
| + expected = '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql'
|
| + if result != expected:
|
| + raise RuntimeError("self-test failed")
|
| +
|
| + # crypt 2
|
| + result = crypt("gnu", '$p5k2$c$u9HvcT4d$.....')
|
| + expected = '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g'
|
| + if result != expected:
|
| + raise RuntimeError("self-test failed")
|
| +
|
| + # crypt 3
|
| + result = crypt("dcl", "tUsch7fU", iterations=13)
|
| + expected = "$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL"
|
| + if result != expected:
|
| + raise RuntimeError("self-test failed")
|
| +
|
| + # crypt 4 (unicode)
|
| + result = crypt(u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2',
|
| + '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ')
|
| + expected = '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ'
|
| + if result != expected:
|
| + raise RuntimeError("self-test failed")
|
| +
|
| +if __name__ == '__main__':
|
| + test_pbkdf2()
|
| +
|
| +# vim:set ts=4 sw=4 sts=4 expandtab:
|
|
|