BabyDES-StatAnal.mws

Baby DES 3
Statistical considerations

İMike May, S.J., 2002, maymk@slu.edu

>    restart:
read `BabyDES.m`:

This worksheet assumes that you have already worked through the first worksheet on Baby DES.  That first Baby DES worksheet creates the BabyDES.m.  The BabyDES.m file should be in the current directory.

This worksheet looks at how effectively Baby DES is when measured from some statistical criteria.  These criteria can be used to evaluate other symmetric ciphers.

For a block cipher to be effective it should be true that if we know all but one of the bits in the plaintext block and the key, then each bit of the cipher block should still have a 50-50 chance of being either zero or one.  Obviously it is too much to ask that precisely 50 % of the bits change when we change one bit of the plaintext.  We want to test if this is the average behavior and if it seems to follow a random pattern..

Shifting each single bit

The first way to test for random behavior is to ask if we start from a fixed plaintext and key, and change each bit of the plaintext one at a time, do the bits of the ciphertext behave randomly.

Counting the number of bits changed in each word

A first examination looks at how many bits of output are changed if we change each single bit of the input or of the key.  Each bit of the output should be changed with a probability of .5.
To make our task easier, we want routines that will easily produce various keys or plaintext messages.  Recall that for Baby DES, the input block is 12 bits and the key is 9 bits.

>    intToBitKey := i ->
  substring(convert(convert(2^9+i,binary),string),2..10):
intToBitMessage := i ->
  substring(convert(convert(2^12+i,binary),string),2..13):

>    zerokey := intToBitKey(0);
zeromessage := intToBitMessage(0);

zerokey :=

zeromessage :=

The first test is to compare the ciphertext obtained from the zerokey and zeromessage with the ciphertext obtained by encoding a message with a single bit changed.  We start by looking at 2 rounds.

>    numRounds := 2;
cipher0 := BabyDES(zeromessage, zerokey, numRounds);
for loc from 0 to 11 do
message := intToBitMessage(2^loc);
cipher := BabyDES(message, zerokey,numRounds);
comparison := xorNbits(cipher, cipher0, 12);
print(loc,message, cipher, comparison);
od:

numRounds := 2

cipher0 :=

0,

1,

2,

3,

4,

5,

6,

7,

8,

9,

10,

11,

Ideally we expect the comparison to be half ones and half zeroes with the ones randomly distributed.  With 2 round Baby DES, we changed an average of 3 bits of output when we changed one round of input.  This is enough less than the hoped for value of 6 bits that we conclude 2 rounds is not enough.

Obviously Baby DES need more than 2 rounds to be effective.  Before testing more rounds we want to produce procedures that will make the results easier to analyze.  We want procedures to convert a bit string into a list of digits and another procedure to count the number of ones in such a list.

>    zeroList12 := [seq(0,place=1..12)]:
bitStringToList12 := bitString ->
   [seq(parse(substring(bitString,place)), place=1..12)]:
addList12 := bitList -> sum(bitList[place], place=1..12):

>    cipher0;
bitStringToList12(cipher0);
addList12(bitStringToList12(cipher0));

[1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0]

6

We now count the number of bits changes by individually changing each bit in the plaintext message and compute the mean and standard deviation of these results.

>    diffCount :=
   [seq(addList12(bitStringToList12(xorNbits(cipher0,
      BabyDES(intToBitMessage(2^i),zerokey,2),12))),i=0..11)];
evalf(stats[describe, mean](diffCount));
evalf(stats[describe, standarddeviation](diffCount));

diffCount := [3, 3, 3, 7, 2, 1, 2, 4, 4, 4, 2, 2]

3.083333333

1.497683397

If each bit was changed with a probability of .5, then we expect the entries of diffCount to look like they were produced by a random binomial probability distribution based on 12 tries with a probability of .5.  The expected value is (12)*(.5)=6.  The expected standard deviation is sqrt(12*.5*.5) = 1.732.  Since we are modeling random behavior we don't expect 6 every time.  We do however expect it to be close to the average value.  (We will return to this later.)

We want to generalize the procedure above so that we can input a key, a message and a number of rounds and we look at the change caused by changing each bit of the plaintext, one at a time.

>    changeEachBitMessage := proc(message, key, rounds)
   local cipher0, diffCount, newMessage, changebits, pos:
   cipher0 := BabyDES(message, key, rounds):
   diffCount := vector(12):
   for pos from 0 to 11 do
      newMessage := xorNbits(message, intToBitMessage(2^pos), 12):
      changebits := xorNbits(cipher0, BabyDES(newMessage, key, rounds), 12):
      diffCount[pos+1] := addList12(bitStringToList12(changebits)):
   od:
   convert(diffCount,list);
end:

>    count1 := changeEachBitMessage(zeromessage, zerokey,2);
evalf(stats[describe, mean](count1));
evalf(stats[describe, standarddeviation](count1));

count1 := [3, 3, 3, 7, 2, 1, 2, 4, 4, 4, 2, 2]

3.083333333

1.497683397

We can try Baby DES with a variable number of rounds and see how many rounds we need for the behavior to look like bits are randomly changed.

>    for rounds from 2 to 10 do
  countList := changeEachBitMessage(zeromessage, zerokey,rounds):
  print(rounds,countList, evalf(stats[describe, mean](countList)),
      evalf(stats[describe, standarddeviation](countList))):
od:

2, [3, 3, 3, 7, 2, 1, 2, 4, 4, 4, 2, 2], 3.083333333, 1.497683397

3, [4, 6, 4, 5, 2, 1, 5, 5, 6, 7, 4, 3], 4.333333333, 1.649915823

4, [5, 9, 7, 6, 3, 2, 8, 4, 4, 7, 8, 4], 5.583333333, 2.139249609

5, [5, 10, 6, 6, 6, 4, 8, 6, 3, 5, 7, 8], 6.166666667, 1.818118685

6, [4, 8, 6, 8, 6, 5, 6, 7, 7, 6, 4, 8], 6.250000000, 1.361677887

7, [3, 7, 5, 8, 4, 4, 7, 6, 8, 6, 5, 4], 5.583333333, 1.605113357

8, [5, 8, 3, 6, 6, 5, 8, 7, 6, 5, 7, 4], 5.833333333, 1.462494065

9, [7, 7, 4, 4, 6, 6, 4, 6, 6, 6, 7, 5], 5.666666667, 1.105541597

10, [6, 6, 6, 2, 6, 7, 4, 4, 6, 7, 6, 6], 5.500000000, 1.384437311

At this first level of analysis, it looks like 5 rounds of Baby DES changes bits with a probability of about .5.  

>   

Random binomial distributions

If each bit is changed with a probability of .5, then the sum of a collection of bits should produce a binomial distribution.  Maple lets us produce random numbers from such a distribution.  We have been looking at numbers that we hope are the result of counting the number of successes in 12 trials, each of which has a .5 chance of success.

>    stats[random,binomiald[12, .5]](1);
stats[random,binomiald[12, .5]](5);
[stats[random,binomiald[12, .5]](12)];

6

5, 5, 6, 6, 7

[3, 7, 6, 7, 5, 5, 7, 3, 4, 9, 8, 6]

Consider the results of 10 fake lists in the form gathered in the last section

>    for counta from 1 to 10 do
fakeList := [stats[random,binomiald[12, .5]](12)]:
  print(counta,fakeList, evalf(stats[describe, mean](fakeList)),
      evalf(stats[describe, standarddeviation](fakeList))):
end do:

1, [7, 8, 9, 4, 4, 6, 6, 5, 5, 7, 8, 7], 6.333333333, 1.545603083

2, [5, 7, 7, 7, 5, 2, 4, 7, 4, 7, 4, 10], 5.750000000, 2.046338193

3, [6, 7, 6, 5, 8, 10, 4, 5, 7, 7, 7, 6], 6.500000000, 1.500000000

4, [4, 8, 5, 6, 7, 4, 6, 6, 8, 7, 8, 6], 6.250000000, 1.361677887

5, [6, 10, 6, 10, 4, 8, 5, 3, 4, 6, 4, 6], 6., 2.198484327

6, [4, 5, 7, 7, 5, 7, 4, 2, 3, 6, 4, 7], 5.083333333, 1.656217242

7, [3, 4, 7, 4, 7, 10, 7, 4, 7, 4, 5, 6], 5.666666667, 1.929306150

8, [8, 6, 5, 9, 7, 4, 8, 7, 9, 8, 8, 6], 7.083333333, 1.497683397

9, [4, 3, 7, 5, 5, 6, 7, 8, 5, 6, 3, 5], 5.333333333, 1.490711985

10, [6, 7, 7, 6, 10, 8, 7, 5, 3, 8, 5, 8], 6.666666667, 1.748014747

Compare with the results of shifting each bit of plaintext for 10 randomly chosen pairs of plaintext and key words with 5 round Baby DES.

>    for counter from 1 to 10 do
   message := intToBitMessage(rand(2^12)()):
   key := intToBitKey(rand(2^9)()):
   countList := changeEachBitMessage(message, key, 5):
   print(message, key, countList, evalf(stats[describe, mean](countList)),
     evalf(stats[describe, standarddeviation](countList))):
od:

The samples from Baby DES seem to be behaving much like the samples from the random distribution.  

As a second line of analysis, we would like to collect the means from samples.  This is easy to do with our fake lists.

>    meanList := list():
for count from 1 to 40 do
   fakeList := [stats[random,binomiald[12, .5]](12)];
   meanList[count] := evalf(stats[describe, mean](fakeList));
od:
meanList := convert(meanList, list):

What we have done is produce a sampling distribution.  The meanList should have the same mean, 6, with a standard distribution that is the old predicted standard deviation divided by the square root  of the size of the list.  Thus the predicted standard deviation is sqrt(12*.5*.5/12)=.5.  In that computation the first 12 is the number of bits collected in defining the binomial distribution and the second 12 is the number of sample taken with that distribution.

>    meanList;
stats[describe, mean](meanList);
stats[describe, standarddeviation](meanList);

[4.833333333, 5.833333333, 6.250000000, 6.666666667, 5.166666667, 7.416666667, 6., 5.833333333, 6.583333333, 5.166666667, 5., 6.166666667, 5.583333333, 6., 5.333333333, 6., 5.666666667, 5.500000000, 5....
[4.833333333, 5.833333333, 6.250000000, 6.666666667, 5.166666667, 7.416666667, 6., 5.833333333, 6.583333333, 5.166666667, 5., 6.166666667, 5.583333333, 6., 5.333333333, 6., 5.666666667, 5.500000000, 5....
[4.833333333, 5.833333333, 6.250000000, 6.666666667, 5.166666667, 7.416666667, 6., 5.833333333, 6.583333333, 5.166666667, 5., 6.166666667, 5.583333333, 6., 5.333333333, 6., 5.666666667, 5.500000000, 5....
[4.833333333, 5.833333333, 6.250000000, 6.666666667, 5.166666667, 7.416666667, 6., 5.833333333, 6.583333333, 5.166666667, 5., 6.166666667, 5.583333333, 6., 5.333333333, 6., 5.666666667, 5.500000000, 5....

5.918749998

.5481938460

We can now do the same thing with samples obtained from the number of bits changed when changing each bit of a random message.

>    changeEachBitMessage2 := proc(message, key, rounds)
   local cipher0, diffCount, newMessage, changebits, pos:
   cipher0 := BabyDES(message, key, rounds):
   diffCount := vector(12):
   for pos from 0 to 11 do
      newMessage := xorNbits(message, intToBitMessage(2^pos), 12):
      changebits := xorNbits(cipher0, BabyDES(newMessage, key, rounds), 12):
      diffCount[pos+1] := addList12(bitStringToList12(changebits)):
   od:
   diffCount := convert(diffCount,list):
   evalf(stats[describe, mean](diffCount));
end:

>    meanList := list():
for count from 1 to 40 do
   message := intToBitMessage(rand(2^12)()):
   key := intToBitKey(rand(2^9)()):
   countList := changeEachBitMessage2(message, key, 5):
   meanList[count] := countList:
od:
meanList := convert(meanList, list);
stats[describe, mean](meanList);
stats[describe, standarddeviation](meanList);

meanList := [5., 5.500000000, 5.250000000, 5.750000000, 6.166666667, 4.916666667, 5.500000000, 6., 5.833333333, 5.500000000, 5.916666667, 6.500000000, 6.583333333, 6.583333333, 5., 6.666666667, 6.08333...
meanList := [5., 5.500000000, 5.250000000, 5.750000000, 6.166666667, 4.916666667, 5.500000000, 6., 5.833333333, 5.500000000, 5.916666667, 6.500000000, 6.583333333, 6.583333333, 5., 6.666666667, 6.08333...
meanList := [5., 5.500000000, 5.250000000, 5.750000000, 6.166666667, 4.916666667, 5.500000000, 6., 5.833333333, 5.500000000, 5.916666667, 6.500000000, 6.583333333, 6.583333333, 5., 6.666666667, 6.08333...
meanList := [5., 5.500000000, 5.250000000, 5.750000000, 6.166666667, 4.916666667, 5.500000000, 6., 5.833333333, 5.500000000, 5.916666667, 6.500000000, 6.583333333, 6.583333333, 5., 6.666666667, 6.08333...

5.831250005

.5337522766

It seems that the bits are behaving randomly from this perspective.

>   

Counting the number of words for which a bit is changed

We initially asked if we change the plaintext one bit at a time, how many bits are changed in each new ciphertext.  The other obvious way to arrange the data asks in how many of the ciphertexts is a particular bit changed.

>    changeEachBitMessage3 := proc(message, key, rounds)
   local cipher0, diffCount, newMessage, changebits, pos:
   cipher0 := BabyDES(message, key, rounds):
   diffCount := [seq(0,i=1..12)]:
   for pos from 0 to 11 do
      newMessage := xorNbits(message, intToBitMessage(2^pos), 12):
      changebits := xorNbits(cipher0, BabyDES(newMessage, key, rounds), 12):
      diffCount := zip(`+`,diffCount,bitStringToList12(changebits)):
   od:
   diffCount:
end:

>    changeEachBitMessage3(zeromessage, zerokey,5);

[8, 4, 9, 4, 6, 3, 6, 9, 8, 6, 5, 6]

If we do this for a number of words we can then accumulate these changes starting at a number of different words

>    key := intToBitKey(rand(2^9)());
countList := []:
for count from 1 to 10 do
   message := intToBitMessage(rand(2^12)()):
   countList := [op(countList), op(changeEachBitMessage3(message, key, 5))]:
od:
countList;

key :=

[5, 5, 6, 6, 7, 9, 6, 6, 4, 7, 6, 3, 6, 8, 4, 7, 8, 4, 4, 6, 5, 3, 9, 7, 7, 5, 8, 6, 6, 8, 7, 5, 9, 8, 5, 8, 6, 7, 7, 5, 5, 7, 3, 6, 4, 9, 9, 8, 3, 8, 5, 7, 6, 6, 7, 5, 3, 4, 7, 9, 6, 4, 4, 6, 4, 7, 5,...
[5, 5, 6, 6, 7, 9, 6, 6, 4, 7, 6, 3, 6, 8, 4, 7, 8, 4, 4, 6, 5, 3, 9, 7, 7, 5, 8, 6, 6, 8, 7, 5, 9, 8, 5, 8, 6, 7, 7, 5, 5, 7, 3, 6, 4, 9, 9, 8, 3, 8, 5, 7, 6, 6, 7, 5, 3, 4, 7, 9, 6, 4, 4, 6, 4, 7, 5,...
[5, 5, 6, 6, 7, 9, 6, 6, 4, 7, 6, 3, 6, 8, 4, 7, 8, 4, 4, 6, 5, 3, 9, 7, 7, 5, 8, 6, 6, 8, 7, 5, 9, 8, 5, 8, 6, 7, 7, 5, 5, 7, 3, 6, 4, 9, 9, 8, 3, 8, 5, 7, 6, 6, 7, 5, 3, 4, 7, 9, 6, 4, 4, 6, 4, 7, 5,...

As above, we take the mean and standard deviation of this list.  It is also useful to tally this list to see the shape of the distribution in countlist.

>    evalf(stats[describe, mean](countList),3);
evalf(stats[describe, standarddeviation](countList),3);
stats[transform,tally](countList);

5.93

1.66

[Weight(2,2), Weight(3,7), Weight(4,16), Weight(5,23), Weight(6,26), Weight(7,23), Weight(8,16), Weight(9,7)]

For comparison, we compute the probability of each value showing up randomly.  For each value of i from 0 to 12, we want to know the probability that we will get i heads from 12 coin flips.

>    probSeq := [seq([i, evalf(combinat[numbcomb](12,i)/combinat[numbcomb](12),3)],i=0..12)];

probSeq := [[0, .244e-3], [1, .293e-2], [2, .161e-1], [3, .537e-1], [4, .121], [5, .193], [6, .226], [7, .193], [8, .121], [9, .537e-1], [10, .161e-1], [11, .293e-2], [12, .244e-3]]
probSeq := [[0, .244e-3], [1, .293e-2], [2, .161e-1], [3, .537e-1], [4, .121], [5, .193], [6, .226], [7, .193], [8, .121], [9, .537e-1], [10, .161e-1], [11, .293e-2], [12, .244e-3]]

This lets us compute the number of times we expect each value of i to show up in our collection of 120 numbers.

>    expectSeq := map(x->[x[1],x[2]*120],probSeq);

expectSeq := [[0, .29280e-1], [1, .35160], [2, 1.9320], [3, 6.4440], [4, 14.520], [5, 23.160], [6, 27.120], [7, 23.160], [8, 14.520], [9, 6.4440], [10, 1.9320], [11, .35160], [12, .29280e-1]]
expectSeq := [[0, .29280e-1], [1, .35160], [2, 1.9320], [3, 6.4440], [4, 14.520], [5, 23.160], [6, 27.120], [7, 23.160], [8, 14.520], [9, 6.4440], [10, 1.9320], [11, .35160], [12, .29280e-1]]

Once again, it looks like the behavior is fairly close to random on this test.  To gain a better perspective, we repeat the process from 10 randomly chosen plaintexts.

>   

>    for i from 1 to 10 do
key := intToBitKey(rand(2^9)());
countList := []:
for count from 1 to 10 do
   message := intToBitMessage(rand(2^12)()):
   countList := [op(countList), op(changeEachBitMessage3(message, key, 5))]:
od:
print(evalf(stats[describe, mean](countList),3),
   evalf(stats[describe, standarddeviation](countList),3),
   stats[transform,tally](countList));
od:

6.02, 1.67, [2, Weight(3,5), Weight(4,18), Weight(5,25), Weight(6,24), Weight(7,23), Weight(8,16), Weight(9,5), Weight(10,3)]

5.68, 2.04, [Weight(2,7), Weight(3,10), Weight(4,21), Weight(5,18), Weight(6,25), Weight(7,14), Weight(8,14), Weight(9,7), Weight(10,3), 11]
5.68, 2.04, [Weight(2,7), Weight(3,10), Weight(4,21), Weight(5,18), Weight(6,25), Weight(7,14), Weight(8,14), Weight(9,7), Weight(10,3), 11]

6.10, 1.69, [Weight(3,9), Weight(4,12), Weight(5,25), Weight(6,22), Weight(7,28), Weight(8,14), Weight(9,8), Weight(10,2)]

5.97, 1.80, [2, Weight(3,5), Weight(4,24), Weight(5,23), Weight(6,23), Weight(7,17), Weight(8,16), Weight(9,8), Weight(10,2), 11]

5.94, 1.62, [Weight(3,6), Weight(4,19), Weight(5,23), Weight(6,32), Weight(7,20), Weight(8,10), Weight(9,8), Weight(10,2)]

6.12, 1.84, [Weight(2,2), Weight(3,6), Weight(4,15), Weight(5,25), Weight(6,23), Weight(7,22), Weight(8,13), Weight(9,10), Weight(10,3), 11]
6.12, 1.84, [Weight(2,2), Weight(3,6), Weight(4,15), Weight(5,25), Weight(6,23), Weight(7,22), Weight(8,13), Weight(9,10), Weight(10,3), 11]

5.58, 1.66, [Weight(2,4), Weight(3,7), Weight(4,20), Weight(5,29), Weight(6,27), Weight(7,17), Weight(8,12), Weight(9,2), Weight(10,2)]
5.58, 1.66, [Weight(2,4), Weight(3,7), Weight(4,20), Weight(5,29), Weight(6,27), Weight(7,17), Weight(8,12), Weight(9,2), Weight(10,2)]

5.84, 1.62, [Weight(2,3), Weight(3,5), Weight(4,19), Weight(5,21), Weight(6,31), Weight(7,20), Weight(8,17), Weight(9,3), 10]

5.90, 1.73, [2, Weight(3,13), Weight(4,11), Weight(5,21), Weight(6,30), Weight(7,25), Weight(8,11), Weight(9,5), Weight(10,3)]

5.79, 2.00, [Weight(2,5), Weight(3,8), Weight(4,23), Weight(5,19), Weight(6,25), Weight(7,14), Weight(8,14), Weight(9,7), Weight(10,4), 11]
5.79, 2.00, [Weight(2,5), Weight(3,8), Weight(4,23), Weight(5,19), Weight(6,25), Weight(7,14), Weight(8,14), Weight(9,7), Weight(10,4), 11]

This does  seem to be close to random behavior.

>   

Clusters of plaintext blocks

A second way to test for randomness is to fix a key and a starting plaintext and look at the aggregated result of encrypting a block of plaintext words.  We will look at a block of 32 words obtained by letting 5 consecutive plaintext bits vary.  We start with some technical procedures for looking at a block of plaintexts.

>    makePlainBlock := proc(startPlain,shiftBits)
   local intShifts,i:
   intShifts := [seq(i*2^shiftBits,i=0..31)]:
   map(xorNbits,map(intToBitMessage,intShifts),startPlain,12):
end:

>    starter :=intToBitMessage(rand(2^12)());
makePlainBlock(starter,7);

starter :=

[
[
[
[

We want a procedure to take each entry in such a block, encrypt it with Baby DES, turn the ciphertext into a list of bits, and add the bits for each position.

>    testPlainBlock := proc(startPlain, key, shiftBits)
   local plainBlock, i, distList:
   plainBlock := makePlainBlock(startPlain, shiftBits):
   distList := [seq(0,i=1..12)]:
   for i from 1 to 32 do
      distList := zip(`+`,distList,bitStringToList12(BabyDES(plainBlock[i],key,5))):
   od:
   distList;
   end:

Now we can test the procedure with a randomly chosen plaintext, key and shift.

>    key := intToBitKey(rand(2^9)());
startPlain := intToBitMessage(rand(2^12)());
shiftBits := rand(8)();
testPlainBlock(startPlain, key, shiftBits);

key :=

startPlain :=

shiftBits := 6

[17, 11, 17, 16, 15, 15, 15, 17, 18, 17, 14, 19]

To do a better test we repeat the process from a number of randomly chosen starting points.  We can take the mean and standard deviation of this distribution.  We expect the distribution to have a mean of 32*.5=16 and a standard deviation of sqrt(32*.5*.5)=2.828.

>    distlist := []:
for i from 1 to 10 do
startPlain := intToBitMessage(rand(2^12)());
d2list := testPlainBlock(startPlain, key, shiftBits);
print(startPlain,d2list, evalf(stats[describe, mean](d2list),3),
   evalf(stats[describe, standarddeviation](d2list),3));
distlist := [op(distlist), op(d2list)]:
od:
distlist;
evalf(stats[describe, mean](distlist),3);
evalf(stats[describe, standarddeviation](distlist),3);

[19, 14, 16, 21, 17, 17, 14, 18, 19, 15, 13, 19, 17, 16, 16, 21, 12, 13, 15, 16, 18, 18, 18, 22, 19, 12, 16, 11, 22, 17, 16, 14, 14, 15, 21, 13, 17, 16, 17, 15, 19, 17, 19, 19, 13, 18, 17, 15, 14, 16, ...
[19, 14, 16, 21, 17, 17, 14, 18, 19, 15, 13, 19, 17, 16, 16, 21, 12, 13, 15, 16, 18, 18, 18, 22, 19, 12, 16, 11, 22, 17, 16, 14, 14, 15, 21, 13, 17, 16, 17, 15, 19, 17, 19, 19, 13, 18, 17, 15, 14, 16, ...
[19, 14, 16, 21, 17, 17, 14, 18, 19, 15, 13, 19, 17, 16, 16, 21, 12, 13, 15, 16, 18, 18, 18, 22, 19, 12, 16, 11, 22, 17, 16, 14, 14, 15, 21, 13, 17, 16, 17, 15, 19, 17, 19, 19, 13, 18, 17, 15, 14, 16, ...
[19, 14, 16, 21, 17, 17, 14, 18, 19, 15, 13, 19, 17, 16, 16, 21, 12, 13, 15, 16, 18, 18, 18, 22, 19, 12, 16, 11, 22, 17, 16, 14, 14, 15, 21, 13, 17, 16, 17, 15, 19, 17, 19, 19, 13, 18, 17, 15, 14, 16, ...

16.4

2.84

Once again the behavior looks fairly close to what we expect from random behavior.

>   

Pairs of plaintext blocks

A final way that we will look at the random behavior is to consider pairs of plaintext words that differ by a fixed but random amount.

Counting the number of bits changed in each wor d

We start by looking at how many bits are changed between the ciphertexts of a pair of plaintexts

>    key := intToBitKey(rand(2^9)());

key :=

>    shiftWord := intToBitMessage(rand(2^12)());

shiftWord :=

>    firstWord := intToBitMessage(rand(2^12)());
secondWord := xorNbits(firstWord, shiftWord, 12);

firstWord :=

secondWord :=

>    cipherDiff := xorNbits(BabyDES(firstWord,key,5),BabyDES(secondWord,key,5),12);

cipherDiff :=

>    diffCount := addList12(bitStringToList12(cipherDiff));

diffCount := 5

Now we create a procedure to do this with a bunch of pairs

>    pairShiftCounter := proc(key, shiftWord, rounds, numPairs)
   local countList, firstWord, secondWord, cipherDiff, i:
   countList := table():
   for i from 1 to numPairs do
      firstWord := intToBitMessage(rand(2^12)());
      secondWord := xorNbits(firstWord, shiftWord, 12);
      cipherDiff := xorNbits(BabyDES(firstWord,key,rounds),
            BabyDES(secondWord,key,rounds),12);
      countList[i] := addList12(bitStringToList12(cipherDiff));
   od:
   convert(countList,list):
end:

>    countList := pairShiftCounter(key, shiftWord, 5, 100);
stats[transform,tally](countList);
evalf(stats[describe, mean](countList));
evalf(stats[describe, standarddeviation](countList));

countList := [7, 6, 6, 6, 6, 5, 5, 10, 6, 8, 5, 8, 5, 3, 6, 6, 5, 6, 6, 5, 6, 7, 2, 7, 8, 3, 6, 9, 8, 8, 7, 6, 6, 6, 6, 7, 6, 6, 4, 2, 8, 4, 6, 6, 10, 7, 6, 6, 9, 9, 4, 6, 8, 8, 5, 5, 7, 6, 7, 5, 7, 8,...
countList := [7, 6, 6, 6, 6, 5, 5, 10, 6, 8, 5, 8, 5, 3, 6, 6, 5, 6, 6, 5, 6, 7, 2, 7, 8, 3, 6, 9, 8, 8, 7, 6, 6, 6, 6, 7, 6, 6, 4, 2, 8, 4, 6, 6, 10, 7, 6, 6, 9, 9, 4, 6, 8, 8, 5, 5, 7, 6, 7, 5, 7, 8,...

[1, Weight(2,2), Weight(3,4), Weight(4,3), Weight(5,18), Weight(6,30), Weight(7,16), Weight(8,18), Weight(9,5), Weight(10,3)]

6.300000000

1.717556404

Once again, the behavior is reasonably close to what we expect from random

>   

Counting the number of words for which a bit is changed

The other way of arranging the data is to look at how many pairs change a particular bit.

>    pairShiftCounter2 := proc(key, shiftWord, rounds, numPairs)
   local countList, firstWord, secondWord, cipherDiff, i:
   countList := [seq(0,i=1..12)]:
   for i from 1 to numPairs do
      firstWord := intToBitMessage(rand(2^12)());
      secondWord := xorNbits(firstWord, shiftWord, 12);
      cipherDiff := xorNbits(BabyDES(firstWord,key,rounds),
            BabyDES(secondWord,key,rounds),12);
      countList := zip(`+`,countList,bitStringToList12(cipherDiff));
   od:
   countList:
end:

>    countList := pairShiftCounter2(key, shiftWord, 5, 100);

countList := [48, 45, 55, 48, 48, 48, 51, 42, 49, 55, 44, 40]

>    stats[transform,tally](countList);
evalf(stats[describe, mean](countList));
evalf(stats[describe, standarddeviation](countList));

[40, 42, 44, 45, Weight(48,4), 49, 51, Weight(55,2)]

47.75000000

4.399337071

It should be noted that the expected mean with 100 pairs of plaintext is 100*.5=50 and the expected standard deviation is sqrt(100*.5*.5)=5.

>   

The ways we have gathered data can obviously be adapted to other cryptographic systems.

One can also do more sophisticated statistical analyses on the gathered data.

>