1 / 45

# Haskell and Cryptography - PowerPoint PPT Presentation

Haskell and Cryptography. Jay-Evan J. Tevis, Ph.D. Department of Computer Science Western Illinois University www.wiu.edu/users/jjt107. Overview. Imperative and Function Programming Paradigms Implementation of the CAST-128 Encryption Algorithm in C and Haskell Results from Student Projects.

I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.

- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript

Jay-Evan J. Tevis, Ph.D.

Department of Computer Science

Western Illinois University

www.wiu.edu/users/jjt107

• Imperative and Function Programming Paradigms

• Implementation of the CAST-128 Encryption Algorithm in C and Haskell

• Results from Student Projects

### Imperative and Functional Programming Paradigms

Data Types

Expressions

Lexical Structure

Procedures

Semantics

Scheme

C/C++

Prolog

Java

Imperative

Denotational

Object-oriented

Axiomatic

Functional

Operational

Logic

Programming Languages

• Assignment

• Control loops

• Environment state

• Array indexing

• Functions and procedures

• Side effects

• Functions with parameters and results

• Binding of parameters

• Recursive calls

• Referential transparency

• Functions as first-class values

• Higher-order functions

• Pattern matching

• Strong typing (both static and dynamic)

• Arbitrary length of numbers

• Polymorphic data typing

• Normal order evaluation

• Based on lambda calculus, which was invented by Alonzo Church

• Named after the mathematician Haskell Curry

• Purely functional programming language

• Started out in the 1980s as a research language

• Stable version of the language is Haskell 98

• Source code is usually translated by an interpreter but can also be compiled

### Implementations of the CAST-128 Encryption Algorithmin C and Haskell

• Invented by Carlisle Adams and defined in RFC 2144, May 1997

• Belongs to the class of encryption algorithms known as Feistel ciphers

• Uses a 12- or 16-round approach with a block size of 64 bits and a key size up to 128 bits

• Creates 32 subkeys from the initial 128-bit key

• Uses eight substitution boxes with 256 entries each

• Uses three different permutation functions based on the round number

• 1.3Ghz, 256MB RAM, Windows XP

• C programming

• jGRASP IDE

• Borland C compiler

• GNU C compiler

• HUGS interpreter

• Requirements analysis: Based on RFC 2144

• Software architecture (High-level design)

• Four modules arranged in a call-and-return architecture

• Incremental development for each module (done in tandem for both C and Haskell)

• Low-level design of functions

• High-level and low-level implementation

• Black box , white box, and integration testing

• Write text file

• Convert chars to block

• Convert block to chars

• Extract a byte from a word

• Create subkey schedule

• Encrypt a block

• Decrypt a block

• Permute a 32-bit word (three functions)

• Rotate a word to the left

• Define eight arrays for the substitution boxes

• Used the same input test values for the similar functions in C and Haskell; compared returned results

• Compared the 32 subkeys created in both the C and Haskell implementations of the key schedule

• Used the test vectors supplied in RFC 2144

• 128-bit key, 64-bit plaintext block, 64-bit ciphertext block

• Encrypted/decrypted documents of various byte lengths

• Text files contained either C source code or HTML

• Decrypted files were tested for byte errors by compiling or browser viewing

void buildEncryptedOutputFile(FILE *inputFilePtr, FILE *outputFilePtr)

{

// Declarations were removed to fit the code on the slide

createSubkeySchedule(key128Bits, subKeySchedule);

while (!EOF_Found)

{

plainBlock[0] = block.pair.left; plainBlock[1] = block.pair.right;

encryptBlock(subKeySchedule, plainBlock, cipherBlock);

block.pair.left = cipherBlock[0]; block.pair.right = cipherBlock[1];

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

fputc(block.array[i], outputFilePtr);

} // buildEncryptedOutputFile

buildOutputFile:: Handle -> Handle -> [Char] -> IO ()

buildOutputFile inFile outFile direction

| (direction == "-e") =

do buildEncryptedOutputFile inFile outFile

(createSubKeySchedule test128BitKey)

buildEncryptedOutputFile:: Handle -> Handle -> KeyScheduleType -> IO ()

buildEncryptedOutputFile inFile outFile keySchedule =

do (inString, endOfFile) <- readUpTo8Characters inFile

block <- charsToBlock inString

outString <- blockToChars (encryptBlock keySchedule block)

hPutStr outFile outString

if (inString!!7 == '\0') then putStr "End of file detected\n"

else buildEncryptedOutputFile inFile outFile keySchedule

• Overall, the C implementation of the basic CAST-128 algorithm was straightforward because RFC 2144 contains C pseudocode

• For any mathematical expressions, the ease or difficulty of implementation in C or Haskell was the same (except for the need to code the rotate left function in C)

• The driver software in both C and Haskell are not tied to the CAST-128 algorithm; consequently, they can be used when implementing other 128-bit key and 64-bit block ciphers

• Use of the array data structure in Haskell greatly simplified the creation of the subkey schedule

• Pattern matching in Haskell relieved the need for condition checking on many of the function input values and permitted a different algorithm approach for subkey creation than the one used in C

• Exception handling in Haskell simplified the need to check for end-of-file when reading the text file

• Strong typing in Haskell ensured that the function interfaces were correct

• Recursion in Haskell made the iterative algorithms much easier and quicker to code, debug, and understand

• C implementation required the use of unsigned numeric types (unsigned long and unsigned char); otherwise, the key building and the encryption/decryption will not work properly

• Both C and Haskell automatically perform modulo 32 arithmetic on the types of unsigned long (in C) and Word32 (in Haskell)

• Source code size for executable statements is nearly the same between C and Haskell; what makes the C code larger are the data declarations

### Results from Student Projects

• RSA encryption algorithm

• Quicksort using temporary files

• HTML to ASCII file converter

• Regular expression evaluation

• C/C++ source code formatter

• String tree-searching algorithm

• Solving a Sudoku puzzle

• The algorithms coded in Haskell are much shorter than those in Java/C++

• Haskell functions are easier to test individually because of their inherent referential transparency

• Haskell syntax “forces” a programmer to write more modular code

• It is simpler to locate and correct errors in a Haskell program

• Haskell code was shorter, more elegant, and easier to test

• Haskell detects and helps prevent type errors

• Haskell lists can be used in lieu of arrays in Java/C++

• Recursive algorithms are straightforward to implement in Haskell

• Haskell abstractions do not consider the limits of the computer’s architecture

• Haskell I/O is more difficult to program with than that of Java/C++

• Haskell could not do exponentiation of larger numbers

• Java/C++ code is easier to read and understand than Haskell code

### Conclusion

• It is time for functional programming to prove its worth

• It is possible to build a complete encryption program in Haskell

• Functional programming may hold the key to building software that is more secure

• Adams, C. RFC 2144: The CAST-128 Encryption Algorithm. (May 1997). www.ietf.org.

• Bird, R. Introduction to Functional Programming using Haskell, 2nd Edition. Prentice Hall, 1998.

• Graff, M. and van Wyk, K. Secure Coding. O'Reilly, 2003.

• Howard, M. and LeBlanc, D. Writing Security Code. Microsoft Press, 2002.

• Hoyte, D. Haskell Implementation of Blowfish. www.hcsw.org. 2002.

• Hudak, P. The Haskell School of Expression. Cambridge University Press, 2000.

• Jones, P. and Hughes. J. Report on the Programming Language Haskell 98. Journal of Functional Programming, Jan 2003.

• Schildt, H. C: The Complete Reference. McGraw-Hill, 2000.

• Viega, J. and McGraw, G. Building Secure Software. Addison-Wesley, 2002.

• Viega, J. and Messier, M. Secure Programming Cookbook. O'Reilly, 2003.

www.wiu.edu/users/jjt107

### Backup Slides

void buildEncryptedOutputFile(FILE *inputFilePtr, FILE *outputFilePtr)

{

// Declarations were removed to fit the code on the slide

createSubkeySchedule(key128Bits, subKeySchedule);

while (!EOF_Found)

{

plainBlock[0] = block.pair.left; plainBlock[1] = block.pair.right;

encryptBlock(subKeySchedule, plainBlock, cipherBlock);

block.pair.left = cipherBlock[0]; block.pair.right = cipherBlock[1];

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

fputc(block.array[i], outputFilePtr);

} // buildOutputFile

buildOutputFile:: Handle -> Handle -> [Char] -> IO ()

buildOutputFile inFile outFile direction

| (direction == "-e") =

do buildEncryptedOutputFile inFile outFile

(createSubKeySchedule test128BitKey)

buildEncryptedOutputFile:: Handle -> Handle -> KeyScheduleType -> IO ()

buildEncryptedOutputFile inFile outFile keySchedule =

do (inString, endOfFile) <- readUpTo8Characters inFile

block <- charsToBlock inString

outString <- blockToChars (encryptBlock keySchedule block)

hPutStr outFile outString

if (inString!!7 == '\0') then putStr "End of file detected\n"

else buildEncryptedOutputFile inFile outFile keySchedule

void createSubkeySchedule(unsigned long key128Bits[], unsigned long subKeys[])

{

// 128-bit key separated into four 32-bit words

unsigned long x0x1x2x3 = key128Bits[0];

unsigned long x4x5x6x7 = key128Bits[1];

unsigned long x8x9xAxB = key128Bits[2];

unsigned long xCxDxExF = key128Bits[3];

unsigned long z0z1z2z3, z4z5z6z7, z8z9zAzB, zCzDzEzF; // Temp 128-bit key

unsigned long x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,xA,xB,xC,xD,xE,xF;

unsigned long z0,z1,z2,z3,z4,z5,z6,z7,z8,z9,zA,zB,zC,zD,zE,zF;

(Shows the function signature and the variable declarations)

z0z1z2z3 = x0x1x2x3 ^ S5[xD] ^ S6[xF] ^ S7[xC] ^ S8[xE] ^ S7[x8];

extractBytes(z0z1z2z3, &z0,&z1,&z2,&z3);

z4z5z6z7 = x8x9xAxB ^ S5[z0] ^ S6[z2] ^ S7[z1] ^ S8[z3] ^ S8[xA];

extractBytes(z4z5z6z7, &z4,&z5,&z6,&z7);

z8z9zAzB = xCxDxExF ^ S5[z7] ^ S6[z6] ^ S7[z5] ^ S8[z4] ^ S5[x9];

extractBytes(z8z9zAzB, &z8,&z9,&zA,&zB);

zCzDzEzF = x4x5x6x7 ^ S5[zA] ^ S6[z9] ^ S7[zB] ^ S8[z8] ^ S6[xB];

extractBytes(zCzDzEzF, &zC,&zD,&zE,&zF);

subKeys[1] = S5[z8] ^ S6[z9] ^ S7[z7] ^ S8[z6] ^ S5[z2];

subKeys[2] = S5[zA] ^ S6[zB] ^ S7[z5] ^ S8[z4] ^ S6[z6];

subKeys[3] = S5[zC] ^ S6[zD] ^ S7[z3] ^ S8[z2] ^ S7[z9];

subKeys[4] = S5[zE] ^ S6[zF] ^ S7[z1] ^ S8[z0] ^ S8[zC];

(Shows a portion of the code to create four keys)

createSubKeySchedule mainKey = array (1,32) ( k1k2k3k4 ++ k5k6k7k8 ++

k9k10k11k12 ++ k13k14k15k16 ++ k17k18k19k20 ++

k21k22k23k24 ++ k25k26k27k28 ++ k29k30k31k32 )

where (xzA, k1k2k3k4) = createK1K2K3K4 (mainKey, [0x0,0x0,0x0,0x0])

(xzB, k5k6k7k8) = createK5K6K7K8 xzA

(xzC, k9k10k11k12) = createK9K10K11K12 xzB

(xzD, k13k14k15k16) = createK13K14K15K16 xzC

(xzE, k17k18k19k20) = createK17K18K19K20 xzD

(xzF, k21k22k23k24) = createK21K22K23K24 xzE

(xzG, k25k26k27k28) = createK25K26K27K28 xzF

(xzH, k29k30k31k32) = createK29K30K31K32 xzG

(Shows how the complete key schedule is brought together)

createK1K2K3K4 :: XZKeysPairType -> (XZKeysPairType, [(Word32,Word32)])

createK1K2K3K4 ((xAlpha:xBeta:xGamma:xOmega:[]),(zAlpha:zBeta:zGamma:zOmega:[])) =

( ((xAlpha:xBeta:xGamma:xOmega:[]),(nzAlpha:nzBeta:nzGamma:nzOmega:[])),

(1,k1):(2,k2):(3,k3):(4,k4):[])

where

nzAlpha = xAlpha `xor` (sBox5!(xOmega#2)) `xor` (sBox6!(xOmega#4)) `xor`

(sBox7!(xOmega#1)) `xor` (sBox8!(xOmega#3)) `xor` (sBox7!(xGamma#1))

nzBeta = xGamma `xor` (sBox5!(nzAlpha#1)) `xor` (sBox6!(nzAlpha#3)) `xor`

(sBox7!(nzAlpha#2)) `xor` (sBox8!(nzAlpha#4)) `xor`

(sBox8!(xGamma#3))

k1 = (sBox5!(nzGamma#1)) `xor` (sBox6!(nzGamma#2)) `xor`

(sBox7!(nzBeta#4)) `xor` (sBox8!(nzBeta#3)) `xor` (sBox5!(nzAlpha#3))

(Shows how each subkey is built)

int readBlockOfCharacters(FILE *inFilePtr, unsigned char buffer[])

{

int i = 0, j, symbol, EOF_Detected = FALSE;

while (i < MAX_BYTES)

{

symbol = fgetc(inFilePtr);

if (symbol == EOF)

{ EOF_Detected = TRUE; break; }

buffer[i] = symbol;

i++;

} // End while

for (j = i; j < MAX_BYTES; j++) buffer[j] = 0;

(Some code was removed to save space)

readUpTo8Characters:: Handle -> IO ([Char], Bool)

do (c1,b1) <- getCharOrNull inputFile; (c2,b2) <- getCharOrNull inputFile

(c3,b3) <- getCharOrNull inputFile; (c4,b4) <- getCharOrNull inputFile

(c5,b5) <- getCharOrNull inputFile; (c6,b6) <- getCharOrNull inputFile

(c7,b7) <- getCharOrNull inputFile; (c8,b8) <- getCharOrNull inputFile

return ( (c1:c2:c3:c4:c5:c6:c7:c8:[]), b8)

where getCharOrNull:: Handle -> IO (Char,Bool)

getCharOrNull inputFile =

do catch (do symbol <- hGetChar inputFile

return (symbol, False) )

(\error -> do return ('\0', True) )

(Show exception handling for end-of-file in Haskell)

typedef struct

{

unsigned long left;

unsigned long right;

} wordPairType;

typedef unsigned char byteBlockType[MAX_BYTES];

typedef union

{

wordPairType pair;

byteBlockType array;

} blockType;

Conversion is done implicitly in both directions in C by means of a union data structure

charsToBlock :: [Char] -> IO [Word32]

charsToBlock (b1:b2:b3:b4:b5:b6:b7:b8:[]) = return [wordLeft, wordRight]

where wordLeft = ((intToWord32 (fromEnum b1)) `shiftL` 24) `xor`

((intToWord32 (fromEnum b2)) `shiftL` 16) `xor`

((intToWord32 (fromEnum b3)) `shiftL` 8) `xor`

wordRight = ((intToWord32 (fromEnum b5)) `shiftL` 24) `xor`

((intToWord32 (fromEnum b6)) `shiftL` 16) `xor`

((intToWord32 (fromEnum b7)) `shiftL` 8) `xor`

blockToChars :: [Word32] -> IO [Char]

blockToChars [wordLeft, wordRight] = return [c1,c2,c3,c4,c5,c6,c7,c8]

where c1 = toEnum (word32ToInt (wordLeft#1))

c2 = toEnum (word32ToInt (wordLeft#2))

c3 = toEnum (word32ToInt (wordLeft#3))

c4 = toEnum (word32ToInt (wordLeft#4))

c5 = toEnum (word32ToInt (wordRight#1))

c6 = toEnum (word32ToInt (wordRight#2))

c7 = toEnum (word32ToInt (wordRight#3))

c8 = toEnum (word32ToInt (wordRight#4))

newLeft = plainBlock[0]; newRight = plainBlock[1];

for (roundCount = 1; roundCount <= MAX_ROUNDS; roundCount++)

{

oldLeft = newLeft; oldRight = newRight; newLeft = oldRight;

if ( (roundCount % 3) == 0)

newRight = oldLeft ^ type3Function(oldRight, subKeys[roundCount],

subKeys[roundCount + 16]);

else if ( (roundCount % 3) == 1)

newRight = oldLeft ^ type1Function(oldRight, subKeys[roundCount],

subKeys[roundCount + 16]);

else if ( (roundCount % 3) == 2)

newRight = oldLeft ^ type2Function(oldRight, subKeys[roundCount],

subKeys[roundCount + 16]);

} // End for

cipherBlock[0] = newRightSide; cipherBlock[1] = newLeftSide;

encryptBlock :: KeyScheduleType -> [Word32] -> [Word32]

encryptBlock keySchedule plainList = auxEncryptBlock keySchedule plainList 1

auxEncryptBlock :: KeyScheduleType -> [Word32] -> Word32 -> [Word32]

-- swap left and right

auxEncryptBlock keySchedule (leftHalf : rightHalf:[]) 17 = (rightHalf : leftHalf : [])

auxEncryptBlock keySchedule (leftHalf : rightHalf:[]) counter =

auxEncryptBlock keySchedule (rightHalf : newRightHalf : []) (counter + 1)

where newRightHalf = leftHalf `xor` (fChoice rightHalf (keySchedule!counter)

(keySchedule!(counter + 16)) counter)

fChoice :: Word32 -> Word32 -> Word32 -> Word32 -> Word32

| (roundNbr `mod` 3) == 1 = type1Function halfBlock maskingKey rotatingKey

| (roundNbr `mod` 3) == 2 = type2Function halfBlock maskingKey rotatingKey

| otherwise = type3Function halfBlock maskingKey rotatingKey

unsigned long type1Function(unsigned long halfBlock, unsigned long maskingKey,

unsigned long rotatingKey)

{

unsigned long Iword, Ia, Ib, Ic, Id, ls5bits, result;

ls5bits = (rotatingKey << 27) >> 27;

Iword = rotateLeft( (maskingKey + halfBlock), ls5bits);

extractBytes(Iword, &Ia,&Ib,&Ic,&Id);

result = ((S1[Ia] ^ S2[Ib]) - S3[Ic]) + S4[Id];

return result;

}

type1Function :: Word32 -> Word32 -> Word32 -> Word32

((sBox1!(word#1) `xor` sBox2!(word#2)) - sBox3!(word#3)) + sBox4!(word#4)

where word = ( (maskingKey + halfBlock) `rotateL` (word32ToInt ls5bits))

ls5bits = ((rotatingKey `shiftL` 27) `shiftR` 27)

void extractBytes( unsigned long word, unsigned long *byte1, unsigned long *byte2,

unsigned long *byte3, unsigned long *byte4)

{

*byte1 = word >> 24;

*byte2 = (word << 8) >> 24;

*byte3 = (word << 16) >> 24;

*byte4 = (word << 24) >> 24;

}

(#) :: Word32 -> Int -> Word32

(#) word position

| position == 1 = word `shiftR` 24

| position == 2 = (word `shiftL` 8) `shiftR` 24

| position == 3 = (word `shiftL` 16) `shiftR` 24

| position == 4 = (word `shiftL` 24) `shiftR` 24

| otherwise = error "Error with extraction operator (#): position invalid"

unsigned long rotateLeft(unsigned long word, unsigned long nbrBitPositions)

{

unsigned long result, i;

result = word;

for (i = 1; i <= nbrBitPositions; i++)

{

// Check if the most significant bit is a one

if (result & MSB_SET_ONLY_NUMBER) // Bitwise AND the result with 2**31

result = (result << 1) + 1;

else

result = (result << 1);

}

return result;

} // End rotateLeft

(Note: rotateL is a library-supplied function in Haskell)