|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""This file implements the chaffing algorithm. |
|
|
|
|
|
Winnowing and chaffing is a technique for enhancing privacy without requiring |
|
|
strong encryption. In short, the technique takes a set of authenticated |
|
|
message blocks (the wheat) and adds a number of chaff blocks which have |
|
|
randomly chosen data and MAC fields. This means that to an adversary, the |
|
|
chaff blocks look as valid as the wheat blocks, and so the authentication |
|
|
would have to be performed on every block. By tailoring the number of chaff |
|
|
blocks added to the message, the sender can make breaking the message |
|
|
computationally infeasible. There are many other interesting properties of |
|
|
the winnow/chaff technique. |
|
|
|
|
|
For example, say Alice is sending a message to Bob. She packetizes the |
|
|
message and performs an all-or-nothing transformation on the packets. Then |
|
|
she authenticates each packet with a message authentication code (MAC). The |
|
|
MAC is a hash of the data packet, and there is a secret key which she must |
|
|
share with Bob (key distribution is an exercise left to the reader). She then |
|
|
adds a serial number to each packet, and sends the packets to Bob. |
|
|
|
|
|
Bob receives the packets, and using the shared secret authentication key, |
|
|
authenticates the MACs for each packet. Those packets that have bad MACs are |
|
|
simply discarded. The remainder are sorted by serial number, and passed |
|
|
through the reverse all-or-nothing transform. The transform means that an |
|
|
eavesdropper (say Eve) must acquire all the packets before any of the data can |
|
|
be read. If even one packet is missing, the data is useless. |
|
|
|
|
|
There's one twist: by adding chaff packets, Alice and Bob can make Eve's job |
|
|
much harder, since Eve now has to break the shared secret key, or try every |
|
|
combination of wheat and chaff packet to read any of the message. The cool |
|
|
thing is that Bob doesn't need to add any additional code; the chaff packets |
|
|
are already filtered out because their MACs don't match (in all likelihood -- |
|
|
since the data and MACs for the chaff packets are randomly chosen it is |
|
|
possible, but very unlikely that a chaff MAC will match the chaff data). And |
|
|
Alice need not even be the party adding the chaff! She could be completely |
|
|
unaware that a third party, say Charles, is adding chaff packets to her |
|
|
messages as they are transmitted. |
|
|
|
|
|
For more information on winnowing and chaffing see this paper: |
|
|
|
|
|
Ronald L. Rivest, "Chaffing and Winnowing: Confidentiality without Encryption" |
|
|
http://theory.lcs.mit.edu/~rivest/chaffing.txt |
|
|
|
|
|
""" |
|
|
|
|
|
__revision__ = "$Id$" |
|
|
|
|
|
from Crypto.Util.number import bytes_to_long |
|
|
|
|
|
class Chaff: |
|
|
"""Class implementing the chaff adding algorithm. |
|
|
|
|
|
Methods for subclasses: |
|
|
|
|
|
_randnum(size): |
|
|
Returns a randomly generated number with a byte-length equal |
|
|
to size. Subclasses can use this to implement better random |
|
|
data and MAC generating algorithms. The default algorithm is |
|
|
probably not very cryptographically secure. It is most |
|
|
important that the chaff data does not contain any patterns |
|
|
that can be used to discern it from wheat data without running |
|
|
the MAC. |
|
|
|
|
|
""" |
|
|
|
|
|
def __init__(self, factor=1.0, blocksper=1): |
|
|
"""Chaff(factor:float, blocksper:int) |
|
|
|
|
|
factor is the number of message blocks to add chaff to, |
|
|
expressed as a percentage between 0.0 and 1.0. blocksper is |
|
|
the number of chaff blocks to include for each block being |
|
|
chaffed. Thus the defaults add one chaff block to every |
|
|
message block. By changing the defaults, you can adjust how |
|
|
computationally difficult it could be for an adversary to |
|
|
brute-force crack the message. The difficulty is expressed |
|
|
as: |
|
|
|
|
|
pow(blocksper, int(factor * number-of-blocks)) |
|
|
|
|
|
For ease of implementation, when factor < 1.0, only the first |
|
|
int(factor*number-of-blocks) message blocks are chaffed. |
|
|
""" |
|
|
|
|
|
if not (0.0<=factor<=1.0): |
|
|
raise ValueError("'factor' must be between 0.0 and 1.0") |
|
|
if blocksper < 0: |
|
|
raise ValueError("'blocksper' must be zero or more") |
|
|
|
|
|
self.__factor = factor |
|
|
self.__blocksper = blocksper |
|
|
|
|
|
|
|
|
def chaff(self, blocks): |
|
|
"""chaff( [(serial-number:int, data:string, MAC:string)] ) |
|
|
: [(int, string, string)] |
|
|
|
|
|
Add chaff to message blocks. blocks is a list of 3-tuples of the |
|
|
form (serial-number, data, MAC). |
|
|
|
|
|
Chaff is created by choosing a random number of the same |
|
|
byte-length as data, and another random number of the same |
|
|
byte-length as MAC. The message block's serial number is |
|
|
placed on the chaff block and all the packet's chaff blocks |
|
|
are randomly interspersed with the single wheat block. This |
|
|
method then returns a list of 3-tuples of the same form. |
|
|
Chaffed blocks will contain multiple instances of 3-tuples |
|
|
with the same serial number, but the only way to figure out |
|
|
which blocks are wheat and which are chaff is to perform the |
|
|
MAC hash and compare values. |
|
|
""" |
|
|
|
|
|
chaffedblocks = [] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
count = len(blocks) * self.__factor |
|
|
blocksper = list(range(self.__blocksper)) |
|
|
for i, wheat in zip(list(range(len(blocks))), blocks): |
|
|
|
|
|
|
|
|
|
|
|
if i < count: |
|
|
serial, data, mac = wheat |
|
|
datasize = len(data) |
|
|
macsize = len(mac) |
|
|
addwheat = 1 |
|
|
|
|
|
for j in blocksper: |
|
|
import sys |
|
|
chaffdata = self._randnum(datasize) |
|
|
chaffmac = self._randnum(macsize) |
|
|
chaff = (serial, chaffdata, chaffmac) |
|
|
|
|
|
|
|
|
if addwheat and bytes_to_long(self._randnum(16)) & 0x40: |
|
|
chaffedblocks.append(wheat) |
|
|
addwheat = 0 |
|
|
chaffedblocks.append(chaff) |
|
|
if addwheat: |
|
|
chaffedblocks.append(wheat) |
|
|
else: |
|
|
|
|
|
chaffedblocks.append(wheat) |
|
|
return chaffedblocks |
|
|
|
|
|
def _randnum(self, size): |
|
|
from Crypto import Random |
|
|
return Random.new().read(size) |
|
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
|
text = """\ |
|
|
We hold these truths to be self-evident, that all men are created equal, that |
|
|
they are endowed by their Creator with certain unalienable Rights, that among |
|
|
these are Life, Liberty, and the pursuit of Happiness. That to secure these |
|
|
rights, Governments are instituted among Men, deriving their just powers from |
|
|
the consent of the governed. That whenever any Form of Government becomes |
|
|
destructive of these ends, it is the Right of the People to alter or to |
|
|
abolish it, and to institute new Government, laying its foundation on such |
|
|
principles and organizing its powers in such form, as to them shall seem most |
|
|
likely to effect their Safety and Happiness. |
|
|
""" |
|
|
print('Original text:\n==========') |
|
|
print(text) |
|
|
print('==========') |
|
|
|
|
|
|
|
|
blocks = [] ; size = 40 |
|
|
for i in range(0, len(text), size): |
|
|
blocks.append( text[i:i+size] ) |
|
|
|
|
|
|
|
|
print('Calculating MACs...') |
|
|
from Crypto.Hash import HMAC, SHA |
|
|
key = 'Jefferson' |
|
|
macs = [HMAC.new(key, block, digestmod=SHA).digest() |
|
|
for block in blocks] |
|
|
|
|
|
assert len(blocks) == len(macs) |
|
|
|
|
|
|
|
|
source = [] |
|
|
m = list(zip(list(range(len(blocks))), blocks, macs)) |
|
|
print(m) |
|
|
for i, data, mac in m: |
|
|
source.append((i, data, mac)) |
|
|
|
|
|
|
|
|
print('Adding chaff...') |
|
|
c = Chaff(factor=0.5, blocksper=2) |
|
|
chaffed = c.chaff(source) |
|
|
|
|
|
from base64 import encodestring |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
wheat = [] |
|
|
print('chaffed message blocks:') |
|
|
for i, data, mac in chaffed: |
|
|
|
|
|
h = HMAC.new(key, data, digestmod=SHA) |
|
|
pmac = h.digest() |
|
|
if pmac == mac: |
|
|
tag = '-->' |
|
|
wheat.append(data) |
|
|
else: |
|
|
tag = ' ' |
|
|
|
|
|
print(tag, '%3d' % i, \ |
|
|
repr(data), encodestring(mac)[:-1]) |
|
|
|
|
|
|
|
|
print('Undigesting wheat...') |
|
|
|
|
|
newtext = "".join(wheat) |
|
|
if newtext == text: |
|
|
print('They match!') |
|
|
else: |
|
|
print('They differ!') |
|
|
|