Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/Crypto/Protocol/AllOrNothing.py: 63%
126 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 07:03 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 07:03 +0000
1#
2# AllOrNothing.py : all-or-nothing package transformations
3#
4# Part of the Python Cryptography Toolkit
5#
6# Written by Andrew M. Kuchling and others
7#
8# ===================================================================
9# The contents of this file are dedicated to the public domain. To
10# the extent that dedication to the public domain is not available,
11# everyone is granted a worldwide, perpetual, royalty-free,
12# non-exclusive license to exercise all rights associated with the
13# contents of this file for any purpose whatsoever.
14# No rights are reserved.
15#
16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
20# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23# SOFTWARE.
24# ===================================================================
26"""This file implements all-or-nothing package transformations.
28An all-or-nothing package transformation is one in which some text is
29transformed into message blocks, such that all blocks must be obtained before
30the reverse transformation can be applied. Thus, if any blocks are corrupted
31or lost, the original message cannot be reproduced.
33An all-or-nothing package transformation is not encryption, although a block
34cipher algorithm is used. The encryption key is randomly generated and is
35extractable from the message blocks.
37This class implements the All-Or-Nothing package transformation algorithm
38described in:
40Ronald L. Rivest. "All-Or-Nothing Encryption and The Package Transform"
41http://theory.lcs.mit.edu/~rivest/fusion.pdf
43"""
45__revision__ = "$Id$"
47import operator
48import sys
49from Crypto.Util.number import bytes_to_long, long_to_bytes
50from Crypto.Util.py3compat import *
51from functools import reduce
53def isInt(x):
54 test = 0
55 try:
56 test += x
57 except TypeError:
58 return 0
59 return 1
61class AllOrNothing:
62 """Class implementing the All-or-Nothing package transform.
64 Methods for subclassing:
66 _inventkey(key_size):
67 Returns a randomly generated key. Subclasses can use this to
68 implement better random key generating algorithms. The default
69 algorithm is probably not very cryptographically secure.
71 """
73 def __init__(self, ciphermodule, mode=None, IV=None):
74 """AllOrNothing(ciphermodule, mode=None, IV=None)
76 ciphermodule is a module implementing the cipher algorithm to
77 use. It must provide the PEP272 interface.
79 Note that the encryption key is randomly generated
80 automatically when needed. Optional arguments mode and IV are
81 passed directly through to the ciphermodule.new() method; they
82 are the feedback mode and initialization vector to use. All
83 three arguments must be the same for the object used to create
84 the digest, and to undigest'ify the message blocks.
85 """
87 self.__ciphermodule = ciphermodule
88 self.__mode = mode
89 self.__IV = IV
90 self.__key_size = ciphermodule.key_size
91 if not isInt(self.__key_size) or self.__key_size==0:
92 self.__key_size = 16
94 __K0digit = bchr(0x69)
96 def digest(self, text):
97 """digest(text:string) : [string]
99 Perform the All-or-Nothing package transform on the given
100 string. Output is a list of message blocks describing the
101 transformed text, where each block is a string of bit length equal
102 to the ciphermodule's block_size.
103 """
105 # generate a random session key and K0, the key used to encrypt the
106 # hash blocks. Rivest calls this a fixed, publically-known encryption
107 # key, but says nothing about the security implications of this key or
108 # how to choose it.
109 key = self._inventkey(self.__key_size)
110 K0 = self.__K0digit * self.__key_size
112 # we need two cipher objects here, one that is used to encrypt the
113 # message blocks and one that is used to encrypt the hashes. The
114 # former uses the randomly generated key, while the latter uses the
115 # well-known key.
116 mcipher = self.__newcipher(key)
117 hcipher = self.__newcipher(K0)
119 # Pad the text so that its length is a multiple of the cipher's
120 # block_size. Pad with trailing spaces, which will be eliminated in
121 # the undigest() step.
122 block_size = self.__ciphermodule.block_size
123 padbytes = block_size - (len(text) % block_size)
124 text = text + b(' ') * padbytes
126 # Run through the algorithm:
127 # s: number of message blocks (size of text / block_size)
128 # input sequence: m1, m2, ... ms
129 # random key K' (`key' in the code)
130 # Compute output sequence: m'1, m'2, ... m's' for s' = s + 1
131 # Let m'i = mi ^ E(K', i) for i = 1, 2, 3, ..., s
132 # Let m's' = K' ^ h1 ^ h2 ^ ... hs
133 # where hi = E(K0, m'i ^ i) for i = 1, 2, ... s
134 #
135 # The one complication I add is that the last message block is hard
136 # coded to the number of padbytes added, so that these can be stripped
137 # during the undigest() step
138 s = divmod(len(text), block_size)[0]
139 blocks = []
140 hashes = []
141 for i in range(1, s+1):
142 start = (i-1) * block_size
143 end = start + block_size
144 mi = text[start:end]
145 assert len(mi) == block_size
146 cipherblock = mcipher.encrypt(long_to_bytes(i, block_size))
147 mticki = bytes_to_long(mi) ^ bytes_to_long(cipherblock)
148 blocks.append(mticki)
149 # calculate the hash block for this block
150 hi = hcipher.encrypt(long_to_bytes(mticki ^ i, block_size))
151 hashes.append(bytes_to_long(hi))
153 # Add the padbytes length as a message block
154 i = i + 1
155 cipherblock = mcipher.encrypt(long_to_bytes(i, block_size))
156 mticki = padbytes ^ bytes_to_long(cipherblock)
157 blocks.append(mticki)
159 # calculate this block's hash
160 hi = hcipher.encrypt(long_to_bytes(mticki ^ i, block_size))
161 hashes.append(bytes_to_long(hi))
163 # Now calculate the last message block of the sequence 1..s'. This
164 # will contain the random session key XOR'd with all the hash blocks,
165 # so that for undigest(), once all the hash blocks are calculated, the
166 # session key can be trivially extracted. Calculating all the hash
167 # blocks requires that all the message blocks be received, thus the
168 # All-or-Nothing algorithm succeeds.
169 mtick_stick = bytes_to_long(key) ^ reduce(operator.xor, hashes)
170 blocks.append(mtick_stick)
172 # we convert the blocks to strings since in Python, byte sequences are
173 # always represented as strings. This is more consistent with the
174 # model that encryption and hash algorithms always operate on strings.
175 return [long_to_bytes(i,self.__ciphermodule.block_size) for i in blocks]
178 def undigest(self, blocks):
179 """undigest(blocks : [string]) : string
181 Perform the reverse package transformation on a list of message
182 blocks. Note that the ciphermodule used for both transformations
183 must be the same. blocks is a list of strings of bit length
184 equal to the ciphermodule's block_size.
185 """
187 # better have at least 2 blocks, for the padbytes package and the hash
188 # block accumulator
189 if len(blocks) < 2:
190 raise ValueError("List must be at least length 2.")
192 # blocks is a list of strings. We need to deal with them as long
193 # integers
194 blocks = list(map(bytes_to_long, blocks))
196 # Calculate the well-known key, to which the hash blocks are
197 # encrypted, and create the hash cipher.
198 K0 = self.__K0digit * self.__key_size
199 hcipher = self.__newcipher(K0)
200 block_size = self.__ciphermodule.block_size
202 # Since we have all the blocks (or this method would have been called
203 # prematurely), we can calculate all the hash blocks.
204 hashes = []
205 for i in range(1, len(blocks)):
206 mticki = blocks[i-1] ^ i
207 hi = hcipher.encrypt(long_to_bytes(mticki, block_size))
208 hashes.append(bytes_to_long(hi))
210 # now we can calculate K' (key). remember the last block contains
211 # m's' which we don't include here
212 key = blocks[-1] ^ reduce(operator.xor, hashes)
214 # and now we can create the cipher object
215 mcipher = self.__newcipher(long_to_bytes(key, self.__key_size))
217 # And we can now decode the original message blocks
218 parts = []
219 for i in range(1, len(blocks)):
220 cipherblock = mcipher.encrypt(long_to_bytes(i, block_size))
221 mi = blocks[i-1] ^ bytes_to_long(cipherblock)
222 parts.append(mi)
224 # The last message block contains the number of pad bytes appended to
225 # the original text string, such that its length was an even multiple
226 # of the cipher's block_size. This number should be small enough that
227 # the conversion from long integer to integer should never overflow
228 padbytes = int(parts[-1])
229 text = b('').join(map(long_to_bytes, parts[:-1]))
230 return text[:-padbytes]
232 def _inventkey(self, key_size):
233 # Return key_size random bytes
234 from Crypto import Random
235 return Random.new().read(key_size)
237 def __newcipher(self, key):
238 if self.__mode is None and self.__IV is None:
239 return self.__ciphermodule.new(key)
240 elif self.__IV is None:
241 return self.__ciphermodule.new(key, self.__mode)
242 else:
243 return self.__ciphermodule.new(key, self.__mode, self.__IV)
247if __name__ == '__main__':
248 import sys
249 import getopt
250 import base64
252 usagemsg = '''\
253Test module usage: %(program)s [-c cipher] [-l] [-h]
255Where:
256 --cipher module
257 -c module
258 Cipher module to use. Default: %(ciphermodule)s
260 --aslong
261 -l
262 Print the encoded message blocks as long integers instead of base64
263 encoded strings
265 --help
266 -h
267 Print this help message
268'''
270 ciphermodule = 'AES'
271 aslong = 0
273 def usage(code, msg=None):
274 if msg:
275 print(msg)
276 print(usagemsg % {'program': sys.argv[0],
277 'ciphermodule': ciphermodule})
278 sys.exit(code)
280 try:
281 opts, args = getopt.getopt(sys.argv[1:],
282 'c:l', ['cipher=', 'aslong'])
283 except getopt.error as msg:
284 usage(1, msg)
286 if args:
287 usage(1, 'Too many arguments')
289 for opt, arg in opts:
290 if opt in ('-h', '--help'):
291 usage(0)
292 elif opt in ('-c', '--cipher'):
293 ciphermodule = arg
294 elif opt in ('-l', '--aslong'):
295 aslong = 1
297 # ugly hack to force __import__ to give us the end-path module
298 module = __import__('Crypto.Cipher.'+ciphermodule, None, None, ['new'])
300 x = AllOrNothing(module)
301 print('Original text:\n==========')
302 print(__doc__)
303 print('==========')
304 msgblocks = x.digest(b(__doc__))
305 print('message blocks:')
306 for i, blk in zip(list(range(len(msgblocks))), msgblocks):
307 # base64 adds a trailing newline
308 print(' %3d' % i, end=' ')
309 if aslong:
310 print(bytes_to_long(blk))
311 else:
312 print(base64.encodestring(blk)[:-1])
313 #
314 # get a new undigest-only object so there's no leakage
315 y = AllOrNothing(module)
316 text = y.undigest(msgblocks)
317 if text == b(__doc__):
318 print('They match!')
319 else:
320 print('They differ!')