Index: third_party/google-endpoints/Crypto/Protocol/AllOrNothing.py |
diff --git a/third_party/google-endpoints/Crypto/Protocol/AllOrNothing.py b/third_party/google-endpoints/Crypto/Protocol/AllOrNothing.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..acc92d5cd3126f1e1cc361e08f6d13d33cda7973 |
--- /dev/null |
+++ b/third_party/google-endpoints/Crypto/Protocol/AllOrNothing.py |
@@ -0,0 +1,319 @@ |
+# |
+# AllOrNothing.py : all-or-nothing package transformations |
+# |
+# Part of the Python Cryptography Toolkit |
+# |
+# Written by Andrew M. Kuchling and others |
+# |
+# =================================================================== |
+# The contents of this file are dedicated to the public domain. To |
+# the extent that dedication to the public domain is not available, |
+# everyone is granted a worldwide, perpetual, royalty-free, |
+# non-exclusive license to exercise all rights associated with the |
+# contents of this file for any purpose whatsoever. |
+# No rights are reserved. |
+# |
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
+# SOFTWARE. |
+# =================================================================== |
+ |
+"""This file implements all-or-nothing package transformations. |
+ |
+An all-or-nothing package transformation is one in which some text is |
+transformed into message blocks, such that all blocks must be obtained before |
+the reverse transformation can be applied. Thus, if any blocks are corrupted |
+or lost, the original message cannot be reproduced. |
+ |
+An all-or-nothing package transformation is not encryption, although a block |
+cipher algorithm is used. The encryption key is randomly generated and is |
+extractable from the message blocks. |
+ |
+This class implements the All-Or-Nothing package transformation algorithm |
+described in: |
+ |
+Ronald L. Rivest. "All-Or-Nothing Encryption and The Package Transform" |
+http://theory.lcs.mit.edu/~rivest/fusion.pdf |
+ |
+""" |
+ |
+__revision__ = "$Id$" |
+ |
+import operator |
+import sys |
+from Crypto.Util.number import bytes_to_long, long_to_bytes |
+from Crypto.Util.py3compat import * |
+ |
+def isInt(x): |
+ test = 0 |
+ try: |
+ test += x |
+ except TypeError: |
+ return 0 |
+ return 1 |
+ |
+class AllOrNothing: |
+ """Class implementing the All-or-Nothing package transform. |
+ |
+ Methods for subclassing: |
+ |
+ _inventkey(key_size): |
+ Returns a randomly generated key. Subclasses can use this to |
+ implement better random key generating algorithms. The default |
+ algorithm is probably not very cryptographically secure. |
+ |
+ """ |
+ |
+ def __init__(self, ciphermodule, mode=None, IV=None): |
+ """AllOrNothing(ciphermodule, mode=None, IV=None) |
+ |
+ ciphermodule is a module implementing the cipher algorithm to |
+ use. It must provide the PEP272 interface. |
+ |
+ Note that the encryption key is randomly generated |
+ automatically when needed. Optional arguments mode and IV are |
+ passed directly through to the ciphermodule.new() method; they |
+ are the feedback mode and initialization vector to use. All |
+ three arguments must be the same for the object used to create |
+ the digest, and to undigest'ify the message blocks. |
+ """ |
+ |
+ self.__ciphermodule = ciphermodule |
+ self.__mode = mode |
+ self.__IV = IV |
+ self.__key_size = ciphermodule.key_size |
+ if not isInt(self.__key_size) or self.__key_size==0: |
+ self.__key_size = 16 |
+ |
+ __K0digit = bchr(0x69) |
+ |
+ def digest(self, text): |
+ """digest(text:string) : [string] |
+ |
+ Perform the All-or-Nothing package transform on the given |
+ string. Output is a list of message blocks describing the |
+ transformed text, where each block is a string of bit length equal |
+ to the ciphermodule's block_size. |
+ """ |
+ |
+ # generate a random session key and K0, the key used to encrypt the |
+ # hash blocks. Rivest calls this a fixed, publically-known encryption |
+ # key, but says nothing about the security implications of this key or |
+ # how to choose it. |
+ key = self._inventkey(self.__key_size) |
+ K0 = self.__K0digit * self.__key_size |
+ |
+ # we need two cipher objects here, one that is used to encrypt the |
+ # message blocks and one that is used to encrypt the hashes. The |
+ # former uses the randomly generated key, while the latter uses the |
+ # well-known key. |
+ mcipher = self.__newcipher(key) |
+ hcipher = self.__newcipher(K0) |
+ |
+ # Pad the text so that its length is a multiple of the cipher's |
+ # block_size. Pad with trailing spaces, which will be eliminated in |
+ # the undigest() step. |
+ block_size = self.__ciphermodule.block_size |
+ padbytes = block_size - (len(text) % block_size) |
+ text = text + b(' ') * padbytes |
+ |
+ # Run through the algorithm: |
+ # s: number of message blocks (size of text / block_size) |
+ # input sequence: m1, m2, ... ms |
+ # random key K' (`key' in the code) |
+ # Compute output sequence: m'1, m'2, ... m's' for s' = s + 1 |
+ # Let m'i = mi ^ E(K', i) for i = 1, 2, 3, ..., s |
+ # Let m's' = K' ^ h1 ^ h2 ^ ... hs |
+ # where hi = E(K0, m'i ^ i) for i = 1, 2, ... s |
+ # |
+ # The one complication I add is that the last message block is hard |
+ # coded to the number of padbytes added, so that these can be stripped |
+ # during the undigest() step |
+ s = divmod(len(text), block_size)[0] |
+ blocks = [] |
+ hashes = [] |
+ for i in range(1, s+1): |
+ start = (i-1) * block_size |
+ end = start + block_size |
+ mi = text[start:end] |
+ assert len(mi) == block_size |
+ cipherblock = mcipher.encrypt(long_to_bytes(i, block_size)) |
+ mticki = bytes_to_long(mi) ^ bytes_to_long(cipherblock) |
+ blocks.append(mticki) |
+ # calculate the hash block for this block |
+ hi = hcipher.encrypt(long_to_bytes(mticki ^ i, block_size)) |
+ hashes.append(bytes_to_long(hi)) |
+ |
+ # Add the padbytes length as a message block |
+ i = i + 1 |
+ cipherblock = mcipher.encrypt(long_to_bytes(i, block_size)) |
+ mticki = padbytes ^ bytes_to_long(cipherblock) |
+ blocks.append(mticki) |
+ |
+ # calculate this block's hash |
+ hi = hcipher.encrypt(long_to_bytes(mticki ^ i, block_size)) |
+ hashes.append(bytes_to_long(hi)) |
+ |
+ # Now calculate the last message block of the sequence 1..s'. This |
+ # will contain the random session key XOR'd with all the hash blocks, |
+ # so that for undigest(), once all the hash blocks are calculated, the |
+ # session key can be trivially extracted. Calculating all the hash |
+ # blocks requires that all the message blocks be received, thus the |
+ # All-or-Nothing algorithm succeeds. |
+ mtick_stick = bytes_to_long(key) ^ reduce(operator.xor, hashes) |
+ blocks.append(mtick_stick) |
+ |
+ # we convert the blocks to strings since in Python, byte sequences are |
+ # always represented as strings. This is more consistent with the |
+ # model that encryption and hash algorithms always operate on strings. |
+ return [long_to_bytes(i,self.__ciphermodule.block_size) for i in blocks] |
+ |
+ |
+ def undigest(self, blocks): |
+ """undigest(blocks : [string]) : string |
+ |
+ Perform the reverse package transformation on a list of message |
+ blocks. Note that the ciphermodule used for both transformations |
+ must be the same. blocks is a list of strings of bit length |
+ equal to the ciphermodule's block_size. |
+ """ |
+ |
+ # better have at least 2 blocks, for the padbytes package and the hash |
+ # block accumulator |
+ if len(blocks) < 2: |
+ raise ValueError, "List must be at least length 2." |
+ |
+ # blocks is a list of strings. We need to deal with them as long |
+ # integers |
+ blocks = map(bytes_to_long, blocks) |
+ |
+ # Calculate the well-known key, to which the hash blocks are |
+ # encrypted, and create the hash cipher. |
+ K0 = self.__K0digit * self.__key_size |
+ hcipher = self.__newcipher(K0) |
+ block_size = self.__ciphermodule.block_size |
+ |
+ # Since we have all the blocks (or this method would have been called |
+ # prematurely), we can calculate all the hash blocks. |
+ hashes = [] |
+ for i in range(1, len(blocks)): |
+ mticki = blocks[i-1] ^ i |
+ hi = hcipher.encrypt(long_to_bytes(mticki, block_size)) |
+ hashes.append(bytes_to_long(hi)) |
+ |
+ # now we can calculate K' (key). remember the last block contains |
+ # m's' which we don't include here |
+ key = blocks[-1] ^ reduce(operator.xor, hashes) |
+ |
+ # and now we can create the cipher object |
+ mcipher = self.__newcipher(long_to_bytes(key, self.__key_size)) |
+ |
+ # And we can now decode the original message blocks |
+ parts = [] |
+ for i in range(1, len(blocks)): |
+ cipherblock = mcipher.encrypt(long_to_bytes(i, block_size)) |
+ mi = blocks[i-1] ^ bytes_to_long(cipherblock) |
+ parts.append(mi) |
+ |
+ # The last message block contains the number of pad bytes appended to |
+ # the original text string, such that its length was an even multiple |
+ # of the cipher's block_size. This number should be small enough that |
+ # the conversion from long integer to integer should never overflow |
+ padbytes = int(parts[-1]) |
+ text = b('').join(map(long_to_bytes, parts[:-1])) |
+ return text[:-padbytes] |
+ |
+ def _inventkey(self, key_size): |
+ # Return key_size random bytes |
+ from Crypto import Random |
+ return Random.new().read(key_size) |
+ |
+ def __newcipher(self, key): |
+ if self.__mode is None and self.__IV is None: |
+ return self.__ciphermodule.new(key) |
+ elif self.__IV is None: |
+ return self.__ciphermodule.new(key, self.__mode) |
+ else: |
+ return self.__ciphermodule.new(key, self.__mode, self.__IV) |
+ |
+ |
+ |
+if __name__ == '__main__': |
+ import sys |
+ import getopt |
+ import base64 |
+ |
+ usagemsg = '''\ |
+Test module usage: %(program)s [-c cipher] [-l] [-h] |
+ |
+Where: |
+ --cipher module |
+ -c module |
+ Cipher module to use. Default: %(ciphermodule)s |
+ |
+ --aslong |
+ -l |
+ Print the encoded message blocks as long integers instead of base64 |
+ encoded strings |
+ |
+ --help |
+ -h |
+ Print this help message |
+''' |
+ |
+ ciphermodule = 'AES' |
+ aslong = 0 |
+ |
+ def usage(code, msg=None): |
+ if msg: |
+ print msg |
+ print usagemsg % {'program': sys.argv[0], |
+ 'ciphermodule': ciphermodule} |
+ sys.exit(code) |
+ |
+ try: |
+ opts, args = getopt.getopt(sys.argv[1:], |
+ 'c:l', ['cipher=', 'aslong']) |
+ except getopt.error, msg: |
+ usage(1, msg) |
+ |
+ if args: |
+ usage(1, 'Too many arguments') |
+ |
+ for opt, arg in opts: |
+ if opt in ('-h', '--help'): |
+ usage(0) |
+ elif opt in ('-c', '--cipher'): |
+ ciphermodule = arg |
+ elif opt in ('-l', '--aslong'): |
+ aslong = 1 |
+ |
+ # ugly hack to force __import__ to give us the end-path module |
+ module = __import__('Crypto.Cipher.'+ciphermodule, None, None, ['new']) |
+ |
+ x = AllOrNothing(module) |
+ print 'Original text:\n==========' |
+ print __doc__ |
+ print '==========' |
+ msgblocks = x.digest(b(__doc__)) |
+ print 'message blocks:' |
+ for i, blk in zip(range(len(msgblocks)), msgblocks): |
+ # base64 adds a trailing newline |
+ print ' %3d' % i, |
+ if aslong: |
+ print bytes_to_long(blk) |
+ else: |
+ print base64.encodestring(blk)[:-1] |
+ # |
+ # get a new undigest-only object so there's no leakage |
+ y = AllOrNothing(module) |
+ text = y.undigest(msgblocks) |
+ if text == b(__doc__): |
+ print 'They match!' |
+ else: |
+ print 'They differ!' |