This demonstration shows a real linear cryptanalysis attack on a modifed version of S-DES.

Script started on Thu May 30 21:51:28 2002

[crandaj@node52-60 cryptanalysis]$ cat pc.cpp

/******************************

*

* pc.cpp

* Jedidiah Crandall, crandaj@erau.edu

* Generates plaintext/ciphertext pairs in a modified version of S-DES

* which has three rounds and no initial or final permutation

*

*******************************/

 

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <stdlib.h>

 

#define BYTE unsigned char

 

char key[25];

char *K1 = &key[0];

char *K2 = &key[8];

char *K3 = &key[16];

 

void SetKey(unsigned long int SetMe)

{

            int i;

            unsigned long int n;

 

            for (i = 0, n = 0x800000; i <= 31; i++, n /= 2)

            {

                        if (SetMe & n)

                                    key[i] = '1';

                        else

                                    key[i] = '0';

            }

            key[32] = '\0';

}

 

int SBox0[4][4] =

{

{1, 0, 3, 2},

{3, 2, 1, 0},

{0, 2, 1, 3},

{3, 1, 3, 2}

};

 

int SBox1[4][4] =

{

{0, 1, 2, 3},

{2, 0, 1, 3},

{3, 0, 1, 0},

{2, 1, 0, 3}

};

 

BYTE BinaryToByte(char *Binary)

{

            int n, i;

            BYTE ReturnMe = 0;

 

            for (i = 0, n = 128; i <= 7; i++, n /= 2)

            {

                        switch(Binary[i])

                        {

                        case '0': break;

                        case '1':

                                    ReturnMe += n;

                        break;

                        default:

                                    puts("BinaryToByte failed");

                                    exit(0);

                        break;

                        }

            }

 

            return ReturnMe;

}

 

void ByteToBinary(char *pBinary, BYTE TheByte)

{

            int i;

            BYTE n;

 

            for (i = 0, n = 0x80; i <= 7; i++, n >>= 1)

            {

                        if (TheByte & n)

                                    pBinary[i] = '1';

                        else

                                    pBinary[i] = '0';

            }

            pBinary[8] = '\0';

}

 

void S0(char *pDest, char *pSrc)

{

            int Row = (pSrc[0] - '0') * 2 + (pSrc[3] - '0');

            int Col = (pSrc[1] - '0') * 2 + (pSrc[2] - '0');

            BYTE LookUp = SBox0[Row][Col];

           

            pDest[0] = '0' + ((LookUp & 0x02) >> 1);

            pDest[1] = '0' + (LookUp & 0x01);

            pDest[2] = '\0';

}

 

void S1(char *pDest, char *pSrc)

{

            int Row = (pSrc[0] - '0') * 2 + (pSrc[3] - '0');

            int Col = (pSrc[1] - '0') * 2 + (pSrc[2] - '0');

            BYTE LookUp = SBox1[Row][Col];

           

            pDest[0] = '0' + ((LookUp & 0x02) >> 1);

            pDest[1] = '0' + (LookUp & 0x01);

            pDest[2] = '\0';

}

 

void P4(char *pDest, char *pS0Output, char *pS1Output)

{

            pDest[0] = pS0Output[1];

            pDest[1] = pS1Output[1];

            pDest[2] = pS1Output[0];

            pDest[3] = pS0Output[0];

            pDest[4] = '\0';

}

 

void XOR4(char *pDest, char *Operand1, char *Operand2)

{

            int i;

            BYTE a, b;

 

            for (i = 0; i <= 3; i++)

            {

                        a = Operand1[i] - '0';

                        b = Operand2[i] - '0';

                        pDest[i] = '0' + (a ^ b);

            }

            pDest[4] = '\0';

}

 

void EP(char *pDest, char *pSrc)

{

            pDest[0] = pSrc[3];

            pDest[1] = pSrc[0];

            pDest[2] = pSrc[1];

            pDest[3] = pSrc[2];

            pDest[4] = pSrc[1];

            pDest[5] = pSrc[2];

            pDest[6] = pSrc[3];

            pDest[7] = pSrc[0];

            pDest[8] = '\0';

}

 

void f(char *pDest, char *pSrc, char *Subkey)

{

            char *pLeft = &pSrc[0], *pRight = &pSrc[4];

            char OutputFromEP[9];

            char OutputFromXOR8[9];

            BYTE XOR8Result;

            char *pS0Input = &OutputFromXOR8[0];

            char *pS1Input = &OutputFromXOR8[4];

            char S0Output[3];

            char S1Output[3];

            char P4Output[5];

            char XOR4Output[5];

 

            EP(OutputFromEP, pRight);

 

            XOR8Result = BinaryToByte(OutputFromEP) ^ BinaryToByte(Subkey);

            ByteToBinary(OutputFromXOR8, XOR8Result);

            S0(S0Output, pS0Input);

            S1(S1Output, pS1Input);

 

            P4(P4Output, S0Output, S1Output);

            XOR4(XOR4Output, P4Output, pLeft);

            memcpy(pDest, XOR4Output, 4);

            memcpy(&pDest[4], pRight, 4);

            pDest[8] = '\0';

}

 

void SW(char *pDest, char *pSrc)

{

            pDest[0] = pSrc[4];

            pDest[1] = pSrc[5];

            pDest[2] = pSrc[6];

            pDest[3] = pSrc[7];

            pDest[4] = pSrc[0];

            pDest[5] = pSrc[1];

            pDest[6] = pSrc[2];

            pDest[7] = pSrc[3];

            pDest[8] = '\0';

}

 

BYTE SDES(BYTE nPlaintext)

{

            BYTE nCiphertext;

            char Plaintext[9];

            char OutOffK1[9];

            char IntofK2[9];

            char OutOffK2[9];

            char IntofK3[9];

            char OutOffK3[9];

 

            ByteToBinary(Plaintext, nPlaintext);

           

 

            f(OutOffK1, Plaintext, K1);

            SW(IntofK2, OutOffK1);

            f(OutOffK2, IntofK2, K2);

            SW(IntofK3, OutOffK2);

            f(OutOffK3, IntofK3, K3);

 

            nCiphertext = BinaryToByte(OutOffK3);

            return nCiphertext;

}

 

int main()

{

            unsigned int DidItAlready[256];

            unsigned int Plaintext;

            unsigned int NumberDone;

            unsigned long int TheKey;

 

            memset(DidItAlready, 0, 256 * sizeof(unsigned int));

 

            fprintf(stderr, "You must enter a hexadecimal key, such as 22B8FA\n");

            fprintf(stderr, "The last two hex digits (FA) are the 8-bit subkey for the last round.\n");

            fprintf(stderr, "Please enter the key you'd like to use:\n");

            scanf("%x", &TheKey);

            SetKey(TheKey);

 

            srand(2233);

 

            NumberDone = 0;

            while (NumberDone < 100)

            {

                        Plaintext = rand() % 256;

                        if (!DidItAlready[Plaintext])

                        {

                                    DidItAlready[Plaintext] = 1;

                                    printf("%d\t%d\n", Plaintext, SDES(Plaintext));

                                    NumberDone++;

                        }

            }

 

            printf("\n");

 

            return 0;

}

 

[crandaj@node52-60 cryptanalysis]$ cat linear.cpp

/****************************************

* linear.cpp

* Jedidiah Crandall, crandaj@erau.edu

* Does linear cryptanalysis on plaintext/ciphertext pairs created with pc.cpp

*****************************************/

 

#include <stdio.h>

#include <stdlib.h>

#include <string.h>#include <stdlib.h>

 

#define BYTE unsigned char

int SBox0[4][4] =

{

{1, 0, 3, 2},

{3, 2, 1, 0},

{0, 2, 1, 3},

{3, 1, 3, 2}

};

 

int SBox1[4][4] =

{

{0, 1, 2, 3},

{2, 0, 1, 3},

{3, 0, 1, 0},

{2, 1, 0, 3}

};

 

BYTE BinaryToByte(char *Binary)

{

            int n, i;

            BYTE ReturnMe = 0;

 

            for (i = 0, n = 128; i <= 7; i++, n /= 2)

            {

                        switch(Binary[i])

                        {

                        case '0': break;

                        case '1':

                                    ReturnMe += n;

                        break;

                        default:

                                    puts("BinaryToByte failed");

                                    exit(0);

                        break;

                        }

            }

 

            return ReturnMe;

}

 

void ByteToBinary(char *pBinary, BYTE TheByte)

{

            int i;

            BYTE n;

 

            for (i = 0, n = 0x80; i <= 7; i++, n >>= 1)

            {

                        if (TheByte & n)

                                    pBinary[i] = '1';

                        else

                                    pBinary[i] = '0';

            }

            pBinary[8] = '\0';

}

 

void S0(char *pDest, char *pSrc)

{

            int Row = (pSrc[0] - '0') * 2 + (pSrc[3] - '0');

            int Col = (pSrc[1] - '0') * 2 + (pSrc[2] - '0');

            BYTE LookUp = SBox0[Row][Col];

           

            pDest[0] = '0' + ((LookUp & 0x02) >> 1);

            pDest[1] = '0' + (LookUp & 0x01);

            pDest[2] = '\0';

}

 

void S1(char *pDest, char *pSrc)

{

            int Row = (pSrc[0] - '0') * 2 + (pSrc[3] - '0');

            int Col = (pSrc[1] - '0') * 2 + (pSrc[2] - '0');

            BYTE LookUp = SBox1[Row][Col];

           

            pDest[0] = '0' + ((LookUp & 0x02) >> 1);

            pDest[1] = '0' + (LookUp & 0x01);

            pDest[2] = '\0';

}

 

void P4(char *pDest, char *pS0Output, char *pS1Output)

{

            pDest[0] = pS0Output[1];

            pDest[1] = pS1Output[1];

            pDest[2] = pS1Output[0];

            pDest[3] = pS0Output[0];

            pDest[4] = '\0';

}

 

void XOR4(char *pDest, char *Operand1, char *Operand2)

{

            int i;

            BYTE a, b;

 

            for (i = 0; i <= 3; i++)

            {

                        a = Operand1[i] - '0';

                        b = Operand2[i] - '0';

                        pDest[i] = '0' + (a ^ b);

            }

            pDest[4] = '\0';

}

 

void EP(char *pDest, char *pSrc)

{

            pDest[0] = pSrc[3];

            pDest[1] = pSrc[0];

            pDest[2] = pSrc[1];

            pDest[3] = pSrc[2];

            pDest[4] = pSrc[1];

            pDest[5] = pSrc[2];

            pDest[6] = pSrc[3];

            pDest[7] = pSrc[0];

            pDest[8] = '\0';

}

 

unsigned int Parity(BYTE n)

{

            unsigned int NumberOfOnes = 0;

            unsigned int Bitmask;

 

            for (Bitmask = 0x01; Bitmask <= 0x80; Bitmask *= 2)

            {

                        if (n & Bitmask)

                                    NumberOfOnes++;

            }

 

            return NumberOfOnes % 2;

}

 

struct t_PCPair

{

            unsigned int Plaintext;

            unsigned int Ciphertext;

};

t_PCPair PCPairs[100];

 

struct t_Candidate

{

            unsigned int Subkey;

            unsigned int Deviation;

};

t_Candidate Candidates[256];

 

void BubbleSort()

{

            unsigned int i, j;

            t_Candidate Temp;

 

            for (i = 0; i <= 255; i++)

                        for (j = i; j <= 255; j++)

                        {

                                    if (Candidates[i].Deviation < Candidates[j].Deviation)

                                    {

                                                Temp = Candidates[i];

                                                Candidates[i] = Candidates[j];

                                                Candidates[j] = Temp;

                                    }

                        }

}

 

int main()

{

            unsigned int Loop;

            unsigned int Subkey;

            unsigned int Q;

            unsigned int S;

            unsigned int RightHalfOfCiphertext;

            unsigned int RightHalfOfPlaintext;

            unsigned int Arrange1, Arrange2;

            unsigned int LeftHalfOfCiphertext;

            char sRightHalfOfCiphertext[9];

            char sEPOutput[9];

 

            char sOutputFromXOR8[9];

            BYTE XOR8Result;

            char *pS0Input = &sOutputFromXOR8[0];

            char *pS1Input = &sOutputFromXOR8[4];

            char sS0Output[3];

            char sS1Output[3];

            char sP4Output[9];

            char sLeftHalfOfCiphertext[9];

            char sEPInput[5];

 

            unsigned int S0Input;

            unsigned int S0Output;

            unsigned int S1Input;

            unsigned int S1Output;

 

            // These are the linear approximations of the S-boxes

            // They are of the form:

            // {4-bit bitmask for S-box input, 2-bit bitmask for S-box output, what it should equal}

            // So {0xa, 0x1, 1} means that the input bits masked by 0xa (1010 - the first and the

            // third) added with the output bits masked by 0x1 (01 -just the second one) modulo 2

            // should equal 1 more than 50% of the time

            unsigned int S0Approx[16][3] =

            {

                        {0xf, 0x3, 1},

                        {0x2, 0x1, 1},

                        {0xa, 0x1, 1},

                        {0xb, 0x2, 1},

                        {0xd, 0x1, 1},

                        {0x3, 0x3, 1},

                        {0x7, 0x3, 1},

                        {0xa, 0x3, 1},

                        {0xf, 0x1, 0},

                        {0x2, 0x3, 0},

                        {0x6, 0x3, 0},

                        {0xb, 0x3, 0},

                        {0x3, 0x2, 0},

                        {0x5, 0x1, 0},

                        {0x5, 0x2, 0},

                        {0xd, 0x2, 0}

            };

            unsigned int S1Approx[16][3] =

            {

                        {0x6, 0x2, 1},

                        {0x2, 0x3, 1},

                        {0xd, 0x3, 1},

                        {0x3, 0x1, 1},

                        {0x5, 0x1, 1},

                        {0xd, 0x1, 1},

                        {0x2, 0x2, 1},

                        {0x3, 0x2, 1},

                        {0x5, 0x3, 0},

                        {0x6, 0x3, 0},

                        {0x7, 0x2, 0},

                        {0x7, 0x3, 0},

                        {0xb, 0x3, 0},

                        {0xc, 0x2, 0},

                        {0xd, 0x2, 0},

                        {0xb, 0x1, 0}

            };

 

            unsigned int TryEquation;

            unsigned int Good;

 

            char sPlaintext[4], sCiphertext[4];

 

            // First, we want to read in 100 plaintext/ciphertext pairs

            for (Loop = 0; Loop < 100; Loop++)

            {

                        scanf("%s\t%s", sPlaintext, sCiphertext);

                        PCPairs[Loop].Plaintext = atoi(sPlaintext);

                        PCPairs[Loop].Ciphertext = atoi(sCiphertext);

            }

 

            // Now, for every possible third-round subkey we want to know how often our linear

            // approximations for the S-boxes in the second round hold

            for (Subkey = 0; Subkey <= 255; Subkey++)

            {

                        Good = 0;

                        for (Loop = 0; Loop < 100; Loop++)

                        {

                                    // Q is the output of the S-boxes in the second round

                                    // We can determine this from the right half of the plaintext

                                    // and the right half of the ciphertext

                                    RightHalfOfCiphertext = PCPairs[Loop].Ciphertext & 0x0f;

                                    RightHalfOfPlaintext = PCPairs[Loop].Plaintext & 0x0f;

                                    Arrange1 = (RightHalfOfCiphertext & 0x01) << 3;

                                    Arrange1 |= (RightHalfOfCiphertext & 0x08) >> 1;

                                    Arrange1 |= (RightHalfOfCiphertext & 0x02);

                                    Arrange1 |= (RightHalfOfCiphertext & 0x04) >> 2;

                                    Arrange2 = (RightHalfOfPlaintext & 0x01) << 3;

                                    Arrange2 |= (RightHalfOfPlaintext & 0x08) >> 1;

                                    Arrange2 |= (RightHalfOfPlaintext & 0x02);

                                    Arrange2 |= (RightHalfOfPlaintext & 0x04) >> 2;

                                    Q = Arrange1 ^ Arrange2;

 

                                    // S is supposed to be the input to the XOR8 in the second round

                                    // which we can make a guess at by guessing the subkey for round 3.

                                    // If we guessed the right subkey for round 3 then we have the actual

                                    // input to the XOR8 in round 2 (S) and the output (Q) of the S-boxes so

                                    // we would expect our linear approximations to hold either true or false

                                    // more than half the time.  If we have made a bad guess for the subkey

                                    // in the third round then our S and Q in the second round won't make any

                                    // sense so our linear approximations will hold about half the time, just

                                    // as if we had selected S and Q randomly.

                                    LeftHalfOfCiphertext = PCPairs[Loop].Ciphertext & 0xf0;

                                    ByteToBinary(sRightHalfOfCiphertext, RightHalfOfCiphertext);

                                    EP(sEPOutput, &sRightHalfOfCiphertext[4]);

 

                                    XOR8Result = BinaryToByte(sEPOutput) ^ Subkey;

                                    ByteToBinary(sOutputFromXOR8, XOR8Result);

                                    S0(sS0Output, pS0Input);

                                    S1(sS1Output, pS1Input);

                                    sP4Output[0] = '0';

                                    sP4Output[1] = '0';

                                    sP4Output[2] = '0';

                                    sP4Output[3] = '0';

                                    P4(&sP4Output[4], sS0Output, sS1Output);

 

                                    ByteToBinary(sLeftHalfOfCiphertext, LeftHalfOfCiphertext);

                                    XOR4(sEPInput, &sP4Output[4], sLeftHalfOfCiphertext);

                                    EP(sEPOutput, sEPInput);

                                    S = BinaryToByte(sEPOutput);

 

                                    S0Input = (S & 0xf0) >> 4;

                                    S1Input = (S & 0x0f);

                                    S0Output = (Q & 0x0C) >> 2;

                                    S1Output = (Q & 0x03);

 

                                    for (TryEquation = 0; TryEquation <= 15; TryEquation++)

                                    {

                                                // Now we want to isolate the correct bits for each equation and add them all up

                                                // modulo 2.  This parity stuff is just a fancy way of doing this.

                                                if ((Parity(S0Input ^ S0Approx[TryEquation][0]) ^ Parity(S0Output ^

 

S0Approx[TryEquation][1])) == S0Approx[TryEquation][2])

                                                            Good++;

                                                if ((Parity(S1Input ^ S1Approx[TryEquation][0]) ^ Parity(S1Output ^

 

S1Approx[TryEquation][1])) == S1Approx[TryEquation][2])

                                                            Good++;

                                    }

                        }

                        Candidates[Subkey].Subkey = Subkey;

                        Candidates[Subkey].Deviation = abs(1600 - Good);

            }

 

            // The good candidates for a subkey K3 are the ones with high deviations

            BubbleSort();

 

            printf("Likely candidates for subkey K3:\n\n");

            printf("Subkey\tDeviation\n-----------------\n");

 

            for (Loop = 0; Loop <= 4; Loop++)

            {

                        printf("%02x\t%d\n", Candidates[Loop].Subkey, Candidates[Loop].Deviation);

            }

 

            return 0;

}

 

 

[crandaj@node52-60 cryptanalysis]$ ./pc > pcout.txt

You must enter a hexadecimal key, such as 22B8FA

The last two hex digits (FA) are the 8-bit subkey for the last round.

Please enter the key you'd like to use:

b2669c

[crandaj@node52-60 cryptanalysis]$ cat pcout.txt

77      48

200     72

100     239

82      54

167     127

63      30

40      217

181     28

96      49

87      242

34      238

231     163

58      100

56      124

1       186

127     130

90      123

61      8

50      155

20      226

86      250

246     157

81      216

103     182

242     36

37      165

140     128

157     108

88      209

234     129

32      141

60      113

199     208

153     51

124     205

249     57

188     196

158     225

224     132

214     198

225     90

117     3

48      43

31      105

168     210

68      116

254     101

59      227

198     88

101     64

45      91

236     93

241     188

202     235

180     240

65      61

240     161

43      212

219     237

85      4

212     45

17      22

69      175

7       41

27      34

150     19

76      121

36      14

144     12

42      122

203     46

135     254

221     18

120     176

195     94

33      224

95      81

133     218

161     107

104     55

176     111

189     6

80      229

206     138

156     53

5       78

213     177

183     232

3       103

118     153

148     131

109     67

74      96

28      162

107     154

248     149

252     184

160     248

21      120

186     195

 

[crandaj@node52-60 cryptanalysis]$ cat pcout.txt | ./linear

Likely candidates for subkey K3:

 

Subkey  Deviation

-----------------

9c      336

bc      336

1b      212

3b      212

4c      186

[crandaj@node52-60 cryptanalysis]$ exit

exit

 

Script done on Thu May 30 21:52:43 2002



This was created as part of the Cryptography Module of NSF Award No. 0113627: "Increasing Security Expertise in Aviation-oriented Computing Education: A Modular Approach", at Embry-Riddle Aeronautical University in Prescott, Arizona.


Project InfoModulesLinksPapersTeamNSF

Last update: August 1, 2002