Python | 229 lines | 188 code | 5 blank | 36 comment | 10 complexity | cc1704d6003575b07376264cfc5d8966 MD5 | raw file
1"""This file implements the chaffing algorithm. 2 3Winnowing and chaffing is a technique for enhancing privacy without requiring 4strong encryption. In short, the technique takes a set of authenticated 5message blocks (the wheat) and adds a number of chaff blocks which have 6randomly chosen data and MAC fields. This means that to an adversary, the 7chaff blocks look as valid as the wheat blocks, and so the authentication 8would have to be performed on every block. By tailoring the number of chaff 9blocks added to the message, the sender can make breaking the message 10computationally infeasible. There are many other interesting properties of 11the winnow/chaff technique. 12 13For example, say Alice is sending a message to Bob. She packetizes the 14message and performs an all-or-nothing transformation on the packets. Then 15she authenticates each packet with a message authentication code (MAC). The 16MAC is a hash of the data packet, and there is a secret key which she must 17share with Bob (key distribution is an exercise left to the reader). She then 18adds a serial number to each packet, and sends the packets to Bob. 19 20Bob receives the packets, and using the shared secret authentication key, 21authenticates the MACs for each packet. Those packets that have bad MACs are 22simply discarded. The remainder are sorted by serial number, and passed 23through the reverse all-or-nothing transform. The transform means that an 24eavesdropper (say Eve) must acquire all the packets before any of the data can 25be read. If even one packet is missing, the data is useless. 26 27There's one twist: by adding chaff packets, Alice and Bob can make Eve's job 28much harder, since Eve now has to break the shared secret key, or try every 29combination of wheat and chaff packet to read any of the message. The cool 30thing is that Bob doesn't need to add any additional code; the chaff packets 31are already filtered out because their MACs don't match (in all likelihood -- 32since the data and MACs for the chaff packets are randomly chosen it is 33possible, but very unlikely that a chaff MAC will match the chaff data). And 34Alice need not even be the party adding the chaff! She could be completely 35unaware that a third party, say Charles, is adding chaff packets to her 36messages as they are transmitted. 37 38For more information on winnowing and chaffing see this paper: 39 40Ronald L. Rivest, "Chaffing and Winnowing: Confidentiality without Encryption" 41http://theory.lcs.mit.edu/~rivest/chaffing.txt 42 43""" 44 45__revision__ = "$Id: Chaffing.py,v 1.7 2003/02/28 15:23:21 akuchling Exp $" 46 47from Crypto.Util.number import bytes_to_long 48 49class Chaff: 50 """Class implementing the chaff adding algorithm. 51 52 Methods for subclasses: 53 54 _randnum(size): 55 Returns a randomly generated number with a byte-length equal 56 to size. Subclasses can use this to implement better random 57 data and MAC generating algorithms. The default algorithm is 58 probably not very cryptographically secure. It is most 59 important that the chaff data does not contain any patterns 60 that can be used to discern it from wheat data without running 61 the MAC. 62 63 """ 64 65 def __init__(self, factor=1.0, blocksper=1): 66 """Chaff(factor:float, blocksper:int) 67 68 factor is the number of message blocks to add chaff to, 69 expressed as a percentage between 0.0 and 1.0. blocksper is 70 the number of chaff blocks to include for each block being 71 chaffed. Thus the defaults add one chaff block to every 72 message block. By changing the defaults, you can adjust how 73 computationally difficult it could be for an adversary to 74 brute-force crack the message. The difficulty is expressed 75 as: 76 77 pow(blocksper, int(factor * number-of-blocks)) 78 79 For ease of implementation, when factor < 1.0, only the first 80 int(factor*number-of-blocks) message blocks are chaffed. 81 """ 82 83 if not (0.0<=factor<=1.0): 84 raise ValueError, "'factor' must be between 0.0 and 1.0" 85 if blocksper < 0: 86 raise ValueError, "'blocksper' must be zero or more" 87 88 self.__factor = factor 89 self.__blocksper = blocksper 90 91 92 def chaff(self, blocks): 93 """chaff( [(serial-number:int, data:string, MAC:string)] ) 94 : [(int, string, string)] 95 96 Add chaff to message blocks. blocks is a list of 3-tuples of the 97 form (serial-number, data, MAC). 98 99 Chaff is created by choosing a random number of the same 100 byte-length as data, and another random number of the same 101 byte-length as MAC. The message block's serial number is 102 placed on the chaff block and all the packet's chaff blocks 103 are randomly interspersed with the single wheat block. This 104 method then returns a list of 3-tuples of the same form. 105 Chaffed blocks will contain multiple instances of 3-tuples 106 with the same serial number, but the only way to figure out 107 which blocks are wheat and which are chaff is to perform the 108 MAC hash and compare values. 109 """ 110 111 chaffedblocks =  112 113 # count is the number of blocks to add chaff to. blocksper is the 114 # number of chaff blocks to add per message block that is being 115 # chaffed. 116 count = len(blocks) * self.__factor 117 blocksper = range(self.__blocksper) 118 for i, wheat in map(None, range(len(blocks)), blocks): 119 # it shouldn't matter which of the n blocks we add chaff to, so for 120 # ease of implementation, we'll just add them to the first count 121 # blocks 122 if i < count: 123 serial, data, mac = wheat 124 datasize = len(data) 125 macsize = len(mac) 126 addwheat = 1 127 # add chaff to this block 128 for j in blocksper: 129 import sys 130 chaffdata = self._randnum(datasize) 131 chaffmac = self._randnum(macsize) 132 chaff = (serial, chaffdata, chaffmac) 133 # mix up the order, if the 5th bit is on then put the 134 # wheat on the list 135 if addwheat and bytes_to_long(self._randnum(16)) & 0x40: 136 chaffedblocks.append(wheat) 137 addwheat = 0 138 chaffedblocks.append(chaff) 139 if addwheat: 140 chaffedblocks.append(wheat) 141 else: 142 # just add the wheat 143 chaffedblocks.append(wheat) 144 return chaffedblocks 145 146 def _randnum(self, size): 147 # TBD: Not a very secure algorithm. 148 # TBD: size * 2 to work around possible bug in RandomPool 149 from Crypto.Util import randpool 150 import time 151 pool = randpool.RandomPool(size * 2) 152 while size > pool.entropy: 153 pass 154 155 # we now have enough entropy in the pool to get size bytes of random 156 # data... well, probably 157 return pool.get_bytes(size) 158 159 160 161if __name__ == '__main__': 162 text = """\ 163We hold these truths to be self-evident, that all men are created equal, that 164they are endowed by their Creator with certain unalienable Rights, that among 165these are Life, Liberty, and the pursuit of Happiness. That to secure these 166rights, Governments are instituted among Men, deriving their just powers from 167the consent of the governed. That whenever any Form of Government becomes 168destructive of these ends, it is the Right of the People to alter or to 169abolish it, and to institute new Government, laying its foundation on such 170principles and organizing its powers in such form, as to them shall seem most 171likely to effect their Safety and Happiness. 172""" 173 print 'Original text:\n==========' 174 print text 175 print '==========' 176 177 # first transform the text into packets 178 blocks =  ; size = 40 179 for i in range(0, len(text), size): 180 blocks.append( text[i:i+size] ) 181 182 # now get MACs for all the text blocks. The key is obvious... 183 print 'Calculating MACs...' 184 from Crypto.Hash import HMAC, SHA 185 key = 'Jefferson' 186 macs = [HMAC.new(key, block, digestmod=SHA).digest() 187 for block in blocks] 188 189 assert len(blocks) == len(macs) 190 191 # put these into a form acceptable as input to the chaffing procedure 192 source =  193 m = map(None, range(len(blocks)), blocks, macs) 194 print m 195 for i, data, mac in m: 196 source.append((i, data, mac)) 197 198 # now chaff these 199 print 'Adding chaff...' 200 c = Chaff(factor=0.5, blocksper=2) 201 chaffed = c.chaff(source) 202 203 from base64 import encodestring 204 205 # print the chaffed message blocks. meanwhile, separate the wheat from 206 # the chaff 207 208 wheat =  209 print 'chaffed message blocks:' 210 for i, data, mac in chaffed: 211 # do the authentication 212 h = HMAC.new(key, data, digestmod=SHA) 213 pmac = h.digest() 214 if pmac == mac: 215 tag = '-->' 216 wheat.append(data) 217 else: 218 tag = ' ' 219 # base64 adds a trailing newline 220 print tag, '%3d' % i, \ 221 repr(data), encodestring(mac)[:-1] 222 223 # now decode the message packets and check it against the original text 224 print 'Undigesting wheat...' 225 newtext = "".join(wheat) 226 if newtext == text: 227 print 'They match!' 228 else: 229 print 'They differ!'