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

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# =================================================================== 

25 

26"""This file implements all-or-nothing package transformations. 

27 

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. 

32 

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. 

36 

37This class implements the All-Or-Nothing package transformation algorithm 

38described in: 

39 

40Ronald L. Rivest. "All-Or-Nothing Encryption and The Package Transform" 

41http://theory.lcs.mit.edu/~rivest/fusion.pdf 

42 

43""" 

44 

45__revision__ = "$Id$" 

46 

47import operator 

48import sys 

49from Crypto.Util.number import bytes_to_long, long_to_bytes 

50from Crypto.Util.py3compat import * 

51from functools import reduce 

52 

53def isInt(x): 

54 test = 0 

55 try: 

56 test += x 

57 except TypeError: 

58 return 0 

59 return 1 

60 

61class AllOrNothing: 

62 """Class implementing the All-or-Nothing package transform. 

63 

64 Methods for subclassing: 

65 

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. 

70 

71 """ 

72 

73 def __init__(self, ciphermodule, mode=None, IV=None): 

74 """AllOrNothing(ciphermodule, mode=None, IV=None) 

75 

76 ciphermodule is a module implementing the cipher algorithm to 

77 use. It must provide the PEP272 interface. 

78 

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 """ 

86 

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 

93 

94 __K0digit = bchr(0x69) 

95 

96 def digest(self, text): 

97 """digest(text:string) : [string] 

98 

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 """ 

104 

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 

111 

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) 

118 

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 

125 

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)) 

152 

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) 

158 

159 # calculate this block's hash 

160 hi = hcipher.encrypt(long_to_bytes(mticki ^ i, block_size)) 

161 hashes.append(bytes_to_long(hi)) 

162 

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) 

171 

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] 

176 

177 

178 def undigest(self, blocks): 

179 """undigest(blocks : [string]) : string 

180 

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 """ 

186 

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.") 

191 

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)) 

195 

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 

201 

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)) 

209 

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) 

213 

214 # and now we can create the cipher object 

215 mcipher = self.__newcipher(long_to_bytes(key, self.__key_size)) 

216 

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) 

223 

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] 

231 

232 def _inventkey(self, key_size): 

233 # Return key_size random bytes 

234 from Crypto import Random 

235 return Random.new().read(key_size) 

236 

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) 

244 

245 

246 

247if __name__ == '__main__': 

248 import sys 

249 import getopt 

250 import base64 

251 

252 usagemsg = '''\ 

253Test module usage: %(program)s [-c cipher] [-l] [-h] 

254 

255Where: 

256 --cipher module 

257 -c module 

258 Cipher module to use. Default: %(ciphermodule)s 

259 

260 --aslong 

261 -l 

262 Print the encoded message blocks as long integers instead of base64 

263 encoded strings 

264 

265 --help 

266 -h 

267 Print this help message 

268''' 

269 

270 ciphermodule = 'AES' 

271 aslong = 0 

272 

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) 

279 

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) 

285 

286 if args: 

287 usage(1, 'Too many arguments') 

288 

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 

296 

297 # ugly hack to force __import__ to give us the end-path module 

298 module = __import__('Crypto.Cipher.'+ciphermodule, None, None, ['new']) 

299 

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!')