AES Encryption
İMike May, S.J., 2002, maymk@slu.edu
| > | restart; |
In working through an implementation of the cryptographic system AES (Rijndael), it is easiest to break the task into several pieces, some of which were explained in previous worksheets. Encryption with AES uses a number of rounds that are made up of 4 basic operations, Byte Substitution (BS), Shift Rows (SR), Mix Columns (MC), and Add Round Key (ARC).
To use these 4 operations we need to think of 8 bit bytes as a number of different data types. The preliminary section is mainly concerned with functions for conversion of data type. These functions were developed in the worksheet on S-Box creation.
Byte Substitution is done with a look up table that is called an S-Box, which was developed in an earlier worksheet. Here we simply recall the results of that worksheet.
The Add Round Key operation XORs a round key with the current message block. The process for expanding the key into a sequence of round keys was developed in a previous worksheet. We recall the results of that worksheet.
Once the SBoxes and key expansion are established it is easy to construct the 4 basic operations used in a round of encryption and put them together to encrypt and decrypt a plaintext.
Having walked carefully through the steps, we then look at one line commands for encryption and decryption and save these commands for future use.
Preliminaries
Data type conversion rules
| > | intToBits := intValue -> substring(convert(convert(intValue+256, binary), string), 2..9): bitToList := bitWord -> [seq(parse(substring(bitWord,i)), i=1..8)]: listToPoly := bitList -> sort(sum(bitList[j]*alpha^(8-j), j=1..8)): polyToInt := poly -> subs(alpha=2, poly): hexTo8Bits := hexPair -> substring(convert(convert( convert(hexPair,decimal,hex)+256,binary),string),2..9): listToBits := bitList -> cat(seq(convert(bitList[i],string),i=1..8)): bitToInt := bitWord -> convert(parse(bitWord),decimal,binary): listToInt := bitList -> sort(sum(bitList[j]*2^(8-j), j=1..8)): polyToList := poly -> [seq(coeff(poly,alpha, 8-i),i=1..8)]: intToHex := intValue -> substring(convert(convert(intValue+256, hex), string), 2..3): polyToBits := poly -> intToBits(polyToInt( poly)): bitToPoly := bitWord -> listToPoly(bitToList(bitWord)): intToPoly := intValue -> listToPoly(bitToList(intToBits(intValue))): |
Rules for converting between lists and matrices
| > | listToMatrix := list -> matrix(4,4,list): listToMatrix2 := list ->linalg[transpose](matrix(4,4,list)): matrixToList := mat -> ListTools[Flatten](convert(mat,listlist)): matrixToList2 := mat -> ListTools[Flatten](convert(linalg[transpose](mat),listlist)): matrixToHex := bitMatrix -> cat(op(matrixToList2( map(x -> intToHex(bitToInt(x)),bitMatrix)))): |
Constants
| > | genPoly := alpha^8 + alpha^4 + alpha^3 + alpha + 1: |
| > | XOR := (a,b) -> if (a=b) then "0" else "1" fi: xorNbits := proc(a,b,N) local aString, bString: aString := convert(a,string): bString := convert(b,string): cat(seq(XOR(substring(aString,i), substring(bString,i)),i=1..N)): end: xor8 := (a,b) -> xorNbits(a,b,8): |
S-Box definition
The S-Box as a lookup table
| > | SBoxTable :=table(["01000010" = "00101100", "10101000" = "11000010", "10110110" = "01001110", "00011010" = "10100010", "00111000" = "00000111", "11011000" = "01100001", "00000101" = "01101011", "00101111" = "00010101", "00110101" = "10010110", "00111111" = "01110101", "10000010" = "00010011", "00000110" = "01101111", "00100011" = "00100110", "10010010" = "01001111", "11000000" = "10111010", "11010000" = "01110000", "10111010" = "11110100", "10010111" = "10001000", "10101011" = "01100010", "11100110" = "10001110", "11101100" = "11001110", "00011101" = "10100100", "00000000" = "01100011", "10100010" = "00111010", "11000001" = "01111000", "00011001" = "11010100", "01101110" = "10011111", "11101011" = "11101001", "11101111" = "11011111", "00100111" = "11001100", "11110100" = "10111111", "00000001" = "01111100", "00011011" = "10101111", "01110111" = "11110101", "11010101" = "00000011", "00010111" = "11110000", "00111100" = "11101011", "10111011" = "11101010", "01000000" = "00001001", "11111011" = "00001111", "01111001" = "10110110", "10110000" = "11100111", "10100100" = "01001001", "11000010" = "00100101", "11110111" = "01101000", "00110100" = "00011000", "01011100" = "01001010", "00001101" = "11010111", "00111110" = "10110010", "01010010" = "00000000", "01001111" = "10000100", "11000101" = "10100110", "11110110" = "01000010", "01110110" = "00111000", "00100010" = "10010011", "00101011" = "11110001", "11001000" = "11101000", "01010110" = "10110001", "10101111" = "01111001", "11011010" = "01010111", "11111010" = "00101101", "00101110" = "00110001", "11011001" = "00110101", "11100000" = "11100001", "01101111" = "10101000", "11100100" = "01101001", "01000101" = "01101110", "00001000" = "00110000", "00000010" = "01110111", "00011000" = "10101101", "00010100" = "11111010", "01000001" = "10000011", "10101110" = "11100100", "10100110" = "00100100", "01110000" = "01010001", "01010100" = "00100000", "11100001" = "11111000", "10111000" = "01101100", "00100110" = "11110111", "10110011" = "01101101", "11000100" = "00011100", "11100111" = "10010100", "11101010" = "10000111", "00001100" = "11111110", "00110110" = "00000101", "01010000" = "01010011", "11101000" = "10011011", "10001110" = "00011001", "11001001" = "11011101", "11001011" = "00011111", "10111100" = "01100101", "00100100" = "00110110", "01011110" = "01011000", "01001000" = "01010010", "10000011" = "11101100", "11100101" = "11011001", "11011101" = "11000001", "00010000" = "11001010", "01001101" = "11100011", "01111010" = "11011010", "11010100" = "01001000", "00101100" = "01110001", "01011011" = "00111001", "01011101" = "01001100", "10011100" = "11011110", "01001110" = "00101111", "11001110" = "10001011", "00011111" = "11000000", "00101010" = "11100101", "11110000" = "10001100", "00110111" = "10011010", "11101101" = "01010101", "11100011" = "00010001", "10011011" = "00010100", "01010001" = "11010001", "10100101" = "00000110", "01011111" = "11001111", "01100001" = "11101111", "10001010" = "01111110", "00101001" = "10100101", "00110000" = "00000100", "01100111" = "10000101", "10101100" = "10010001", "11010010" = "10110101", "00100000" = "10110111", "10000101" = "10010111", "01101001" = "11111001", "00000100" = "11110010", "10110100" = "10001101", "01100010" = "10101010", "01101010" = "00000010", "01000111" = "10100000", "00111001" = "00010010", "11010001" = "00111110", "10010011" = "11011100", "10011101" = "01011110", "01110101" = "10011101", "11001111" = "10001010", "00001011" = "00101011", "01111111" = "11010010", "11010011" = "01100110", "00011100" = "10011100", "11111110" = "10111011", "00011110" = "01110010", "10000001" = "00001100", "10001011" = "00111101", "00010110" = "01000111", "10010100" = "00100010", "10110101" = "11010101", "01110011" = "10001111", "11011111" = "10011110", "11111001" = "10011001", "10101010" = "10101100", "10111101" = "01111010", "11000111" = "11000110", "11110010" = "10001001", "01101000" = "01000101", "10010000" = "01100000", "00110001" = "11000111", "01011000" = "01101010", "10000000" = "11001101", "10000100" = "01011111", "10001111" = "01110011", "10110111" = "10101001", "10011000" = "01000110", "01111101" = "11111111", "10000111" = "00010111", "11110101" = "11100110", "11000110" = "10110100", "10110010" = "00110111", "01101011" = "01111111", "01111100" = "00010000", "10110001" = "11001000", "00111011" = "11100010", "01000100" = "00011011", "11111111" = "00010110", "01100110" = "00110011", "01111110" = "11110011", "10100001" = "00110010", "00101101" = "11011000", "01011010" = "10111110", "00111101" = "00100111", "10101101" = "10010101", "10000110" = "01000100", "00010010" = "11001001", "11110001" = "10100001", "01101101" = "00111100", "10011010" = "10111000", "01000110" = "01011010", "01001001" = "00111011", "10100000" = "11100000", "11101110" = "00101000", "11111101" = "01010100", "00001111" = "01110110", "00111010" = "10000000", "11100010" = "10011000", "01110001" = "10100011", "11111000" = "01000001", "11000011" = "00101110", "00010101" = "01011001", "01010011" = "11101101", "01110010" = "01000000", "10111110" = "10101110", "00001110" = "10101011", "00010001" = "10000010", "10001100" = "01100100", "01001010" = "11010110", "10101001" = "11010011", "11001100" = "01001011", "10001101" = "01011101", "11001010" = "01110100", "10001001" = "10100111", "10010001" = "10000001", "11011011" = "10111001", "00010011" = "01111101", "00100101" = "00111111", "01100000" = "11010000", "01001100" = "00101001", "10011111" = "11011011", "11101001" = "00011110", "00001001" = "00000001", "00000111" = "11000101", "01011001" = "11001011", "01111011" = "00100001", "10001000" = "11000100", "10111111" = "00001000", "00101000" = "00110100", "10100111" = "01011100", "10111001" = "01010110", "00000011" = "01111011", "00110010" = "00100011", "01001011" = "10110011", "11110011" = "00001101", "01010111" = "01011011", "11011100" = "10000110", "10010101" = "00101010", "10010110" = "10010000", "01100011" = "11111011", "01100101" = "01001101", "11010111" = "00001110", "01110100" = "10010010", "11001101" = "10111101", "01010101" = "11111100", "01101100" = "01010000", "10100011" = "00001010", "11111100" = "10110000", "10011110" = "00001011", "11011110" = "00011101", "01111000" = "10111100", "10011001" = "11101110", "01000011" = "00011010", "11010110" = "11110110", "00001010" = "01100111", "00110011" = "11000011", "01100100" = "01000011", "00100001" = "11111101"]): |
The inverse S-Box as a lookup table
| > | InvSBoxTable := table(["00000010" = "01101010", "01101001" = "11100100", "01100000" = "10010000", "00001111" = "11111011", "00010100" = "10011011", "00100000" = "01010100", "11101100" = "10000011", "10111110" = "01011010", "01101100" = "10111000", "10001000" = "10010111", "01011110" = "10011101", "00100100" = "10100110", "11111000" = "11100001", "00111110" = "11010001", "11011010" = "01111010", "11110000" = "00010111", "10111000" = "10011010", "00101001" = "01001100", "11100101" = "00101010", "00000111" = "00111000", "10110101" = "11010010", "00111001" = "01011011", "11100000" = "10100000", "11110001" = "00101011", "01110011" = "10001111", "00100111" = "00111101", "00101000" = "11101110", "11110110" = "11010110", "01010011" = "01010000", "00001011" = "10011110", "11001001" = "00010010", "00001000" = "10111111", "00000100" = "00110000", "00101010" = "10010101", "10010000" = "10010110", "10101100" = "10101010", "01100110" = "11010011", "10111111" = "11110100", "01000011" = "01100100", "00001101" = "11110011", "10000001" = "10010001", "01001001" = "10100100", "01001110" = "10110110", "00110000" = "00001000", "10100101" = "00101001", "10001010" = "11001111", "11110101" = "01110111", "00000110" = "10100101", "10111010" = "11000000", "10110010" = "00111110", "11110111" = "00100110", "00111111" = "00100101", "00000000" = "01010010", "11001110" = "11101100", "10101111" = "00011011", "00110011" = "01100110", "01111011" = "00000011", "10000010" = "00010001", "11101010" = "10111011", "10110001" = "01010110", "11000101" = "00000111", "10000011" = "01000001", "11110011" = "01111110", "10111011" = "11111110", "01101101" = "10110011", "10001111" = "01110011", "01101010" = "01011000", "00100010" = "10010100", "01000101" = "01101000", "00000001" = "00001001", "00010010" = "00111001", "01010000" = "01101100", "10011101" = "01110101", "10101011" = "00001110", "10000110" = "11011100", "00001110" = "11010111", "11011000" = "00101101", "01011111" = "10000100", "10001011" = "11001110", "01100101" = "10111100", "10111101" = "11001101", "10010011" = "00100010", "10000100" = "01001111", "11101111" = "01100001", "11001000" = "10110001", "11110100" = "10111010", "00101110" = "11000011", "10010010" = "01110100", "00110010" = "10100001", "10011100" = "00011100", "00100011" = "00110010", "00101011" = "00001011", "01010100" = "11111101", "11011001" = "11100101", "10101001" = "10110111", "01110101" = "00111111", "11011100" = "10010011", "10011011" = "11101000", "01010101" = "11101101", "11000110" = "11000111", "01100010" = "10101011", "10101101" = "00011000", "11100100" = "10101110", "01000111" = "00010110", "00010101" = "00101111", "11010110" = "01001010", "10011110" = "11011111", "01001010" = "01011100", "10100110" = "11000101", "01111000" = "11000001", "01111101" = "00010011", "00010111" = "10000111", "10000101" = "01100111", "00100110" = "00100011", "01111100" = "00000001", "11011111" = "11101111", "00011111" = "11001011", "11001011" = "01011001", "01001100" = "01011101", "01110010" = "00011110", "01101011" = "00000101", "01000010" = "11110110", "10011010" = "00110111", "11111110" = "00001100", "00011010" = "01000011", "10110110" = "01111001", "10100001" = "11110001", "11100011" = "01001101", "01100011" = "00000000", "11001100" = "00100111", "11010000" = "01100000", "01110110" = "00001111", "00111010" = "10100010", "10100111" = "10001001", "01001111" = "10010010", "11000001" = "11011101", "11110010" = "00000100", "10100010" = "00011010", "01011000" = "01011110", "10110100" = "11000110", "01100001" = "11011000", "01010111" = "11011010", "11101001" = "11101011", "00011000" = "00110100", "01011010" = "01000110", "11101000" = "11001000", "10101010" = "01100010", "00110100" = "00101000", "00010000" = "01111100", "01111110" = "10001010", "00100101" = "11000010", "11111011" = "01100011", "11100001" = "11100000", "01101000" = "11110111", "11111010" = "00010100", "10010001" = "10101100", "11000100" = "10001000", "10001101" = "10110100", "00011001" = "10001110", "01011100" = "10100111", "01010010" = "01001000", "10011000" = "11100010", "10100000" = "01000111", "01000000" = "01110010", "00111011" = "01001001", "10001001" = "11110010", "01111010" = "10111101", "01101111" = "00000110", "11111001" = "01101001", "00101111" = "01001110", "11101110" = "10011001", "11010101" = "10110101", "11001010" = "00010000", "10100011" = "01110001", "10101110" = "10111110", "01001011" = "11001100", "00011011" = "01000100", "11101011" = "00111100", "10000111" = "11101010", "11000011" = "00110011", "00101100" = "01000010", "10010111" = "10000101", "01010110" = "10111001", "01110001" = "00101100", "11011110" = "10011100", "00000011" = "11010101", "10110000" = "11111100", "01110100" = "11001010", "11011011" = "10011111", "11000000" = "00011111", "10010110" = "00110101", "00001100" = "10000001", "00010011" = "10000010", "01011101" = "10001101", "10001100" = "11110000", "00110101" = "11011001", "00111000" = "01110110", "01000100" = "10000110", "10010100" = "11100111", "11010111" = "00001101", "10110111" = "00100000", "00111100" = "01101101", "01000001" = "11111000", "11111101" = "00100001", "00011101" = "11011110", "10101000" = "01101111", "01011001" = "00010101", "01111001" = "10101111", "11111111" = "01111101", "10110011" = "01001011", "01110000" = "11010000", "10111001" = "11011011", "00110001" = "00101110", "01010001" = "01110000", "00011100" = "11000100", "11000010" = "10101000", "11010100" = "00011001", "00010001" = "11100011", "01110111" = "00000010", "11101101" = "01010011", "11001101" = "10000000", "01100100" = "10001100", "11000111" = "00110001", "00010110" = "11111111", "10000000" = "00111010", "11100110" = "11110101", "10011001" = "11111001", "00001001" = "01000000", "01000110" = "10011000", "11010001" = "01010001", "10011111" = "01101110", "00101101" = "11111010", "00001010" = "10100011", "11111100" = "01010101", "10111100" = "01111000", "00100001" = "01111011", "10100100" = "00011101", "11100010" = "00111011", "11011101" = "11001001", "00110110" = "00100100", "00111101" = "10001011", "11001111" = "01011111", "10001110" = "11100110", "11010011" = "10101001", "11100111" = "10110000", "00000101" = "00110110", "11010010" = "01111111", "00011110" = "11101001", "01100111" = "00001010", "10010101" = "10101101", "01001101" = "01100101", "01111111" = "01101011", "00110111" = "10110010", "01001000" = "11010100", "01101110" = "01000101", "01011011" = "01010111"]): |
Key Expansion
| > | roundFudge := int -> Rem(alpha^(int-1),genPoly,alpha) mod 2: polyToInt := poly -> subs(alpha=2, poly): roundFudgeWord := int -> polyToBits(roundFudge(int)): randKeyGenerator := () -> map(intToBits, [seq(rand(0..255)(),i=1..16)]): hex32ToKey: hexWord ->map(hexTo8Bits, [seq(substring(hexWord,2*i-1..2*i),i=1..16)]): |
| > | keyExpander := proc(keyList) local keyExpanded, i, j, k, fudgeWord: keyExpanded := matrix(4,44): for j from 1 to 4 do for i from 1 to 4 do keyExpanded[i,j] := keyList[(j-1)*4+i]; end do: end do: for i from 1 to 10 do fudgeWord := roundFudgeWord(i); keyExpanded[1,4*i+1] := xor8(keyExpanded[1,4*i-3],SBoxTable[keyExpanded[2,4*i]]); keyExpanded[2,4*i+1] := xor8(keyExpanded[2,4*i-3],SBoxTable[keyExpanded[3,4*i]]); keyExpanded[3,4*i+1] := xor8(keyExpanded[3,4*i-3],SBoxTable[keyExpanded[4,4*i]]); keyExpanded[4,4*i+1] := xor8(keyExpanded[4,4*i-3],SBoxTable[keyExpanded[1,4*i]]); keyExpanded[1,4*i+1] := xor8(keyExpanded[1,4*i+1],fudgeWord); for j from 2 to 4 do for k from 1 to 4 do keyExpanded[k,4*i+j] :=xor8(keyExpanded[k,4*i+j-4],keyExpanded[k,4*i+j-1]): end do: end do: end do: keyExpanded; end: |
Round Pieces
We are ready to set up our 4 operations needed for encryption. We will create the inverse operations at the same time.
The first operation is Byte Substitution. It is done with a simple look-up table. The inverse operation uses the inverse look-up table,
| > | BS := byteMatrix -> map(x->SBoxTable[x], byteMatrix): InvBS := byteMatrix -> map(x->InvSBoxTable[x], byteMatrix): |
For Shift Rows, the current message state is viewed as a 4 by 4 matrix of bytes. The 4 rows are rotated by 0, 1, 2, and 3 places respectively. (If we were to number rows of a matrix starting with zero then the ith row is rotated by i places.) The inverse operation is a rotation of the 4 rows by 0, 3, 2, and 1 places respectively.
| > | SR := proc(byteMatrix) local byteList, rotList: byteList := convert(byteMatrix,listlist): rotList :=[byteList[1], ListTools[Rotate](byteList[2],1), ListTools[Rotate](byteList[3],2), ListTools[Rotate](byteList[4],3)]: convert(rotList, matrix); end: InvSR := proc(byteMatrix) local byteList, rotList: byteList := convert(byteMatrix,listlist): rotList :=[byteList[1], ListTools[Rotate](byteList[2],3), ListTools[Rotate](byteList[3],2), ListTools[Rotate](byteList[4],1)]: convert(rotList, matrix); end: |
For the Mix Column operation, the message state is considered to be a 4 by 4 matrix of bytes with the bytes understood as polynomials in GF(256). This matrix is multiplied by a set 4 by 4 mixing matrix, with the operations understood as being in GF(256). The inverse operation involves multiplying by the inverse of the mixing matrix.
| > | MixMat := map(intToPoly, matrix(4,4,[2,3,1,1,1,2,3,1,1,1,2,3,3,1,1,2])): MC := proc(byteMatrix) local product1, polyMatrix: polyMatrix := map(bitToPoly, byteMatrix): product1 := linalg[multiply](MixMat,polyMatrix): map(x->polyToBits(sort(Rem(expand(x),genPoly,alpha) mod 2)), product1); end: InvMixMat := map(intToPoly, matrix(4,4,[14,11,13,9,9,14,11,13,13,9,14,11,11,13,9,14])): InvMC := proc(byteMatrix) local product1, polyMatrix: polyMatrix := map(bitToPoly, byteMatrix): product1 := linalg[multiply](InvMixMat,polyMatrix): map(x->polyToBits(sort(Rem(expand(x),genPoly,alpha) mod 2)), product1); end: |
The Add Round Key operation XORs the message state with the round key. The inverse operation is the same.
| > | ARK := proc(byteMatrix, expandedKey, roundNum) local roundKey: roundKey := linalg[transpose] (matrix([linalg[col](expandedKey,(roundNum*4+1)..roundNum*4+4)])): zip(xor8,byteMatrix,roundKey); end: |
Establishing a Message and Key
We are now ready to put the pieces together for AES.
For our first message and key we want to use the hex string "000102030405060708090A0B0C0D0E0F". The reason for such a strange choice is that this was used for the intermediate values test that was part of the original submission of Daemen and Rijmen when they submitted Rijndael in the competition to establish AES. Using the same plaintext and key means that the round key addition for the 0th round will result in a message state of all zeroes. The other intermediate values are:
PT =000102030405060708090A0B0C0D0E0F
CT1=B5C9179EB1CC1199B9C51B92B5C8159D
CT2=2B65F6374C427C5B2FE3A9256896755B
CT3=D1015FCBB4EF65679688462076B9D6AD
CT4=8E17064A2A35A183729FE59FF3A591F1
CT5=D7557DD55999DB3259E2183D558DCDD2
CT6=73A96A5D7799A5F3111D2B63684B1F7F
CT7=1B6B853069EEFC749AFEFD7B57A04CD1
CT8=107EEADFB6F77933B5457A6F08F046B2
CT9=8EC166481A677AA96A14FF6ECE88C010
CT =0A940BB5416EF045F1C39458C653EA5A
| > | testString:= "000102030405060708090A0B0C0D0E0F": testHexList := [seq(substring(testString,2*i-1..2*i),i=1..16)]; |
| > | testByteList := map(hexTo8Bits, testHexList): testKey := testByteList; messMatrix := listToMatrix2(testByteList); |
| > | testKeyExpanded := keyExpander(testKey): |
Round by Round Encryption and Decryption
Now we are ready for the rounds of encryption. The 0th round simply adds the round key. The first through ninth rounds do BS, SR, MC, and ARK in order. The tenth round does not use MC, so it is BS, SR, and ARK in order. After each round we convert the message state to a 32 character hex string to compare to the known values.
| > | cipher := ARK(messMatrix, testKeyExpanded, 0); hexState := matrixToHex(cipher); for i from 1 to 9 do cipher := ARK(MC(SR(BS(cipher))),testKeyExpanded,i); hexState := matrixToHex(cipher); end do; cipher := ARK(SR(BS(cipher)),testKeyExpanded,10); cipherHex := matrixToHex(cipher); |
A quick comparison shows that we have the correct answer and the correct result after each round..
The next step is to reverse the process and decrypt the message. We want to break the ciphertext into a list of strings two character long and convert the list to a matrix of bytes.
| > | ListCipher := [seq(substring(cipherHex,2*i-1..2*i), i = 1..16)]; cipherByteMatrix := map(hexTo8Bits,listToMatrix2(ListCipher)); |
Now we undo the 10 rounds of encryption.
| > | plain := cipherByteMatrix; plain := InvBS(InvSR(ARK(plain, testKeyExpanded,10))); for i from 1 to 9 do plain := InvBS(InvSR(InvMC(ARK(plain,testKeyExpanded,10-i)))); end do; decryptMatrix := ARK(plain, testKeyExpanded, 0); |
We want to convert the message back to a hex string to recover our original message.
| > | matrixToHex(decryptMatrix); |
Single Command Encryption and Decryption
Hex messages and keys
The collection of procedures above can be collected into single procedures that work with both the message and key given as hex strings. (This will be useful for testing.)
| > | encryptAEShex := proc(messageHEX, keyHEX) local expandedKey, cipher, cipherHex, i, messMatrix, keyList: keyList := map(hexTo8Bits, [seq(substring(keyHEX,2*i-1..2*i),i=1..16)]): expandedKey := keyExpander(keyList): messMatrix := listToMatrix2(map(hexTo8Bits, [seq(substring(messageHEX,2*i-1..2*i),i=1..16)])): cipher := ARK(messMatrix, expandedKey, 0): for i from 1 to 9 do cipher := ARK(MC(SR(BS(cipher))),expandedKey,i): end do: cipher := ARK(SR(BS(cipher)),expandedKey,10): cipherHex := matrixToHex(cipher): end: |
| > | decryptAEShex := proc(cipherText, keyHEX) local expandedKey, ListCipher, cipherByteMatrix, plain, i,decryptMatrix, keyList: keyList := map(hexTo8Bits, [seq(substring(keyHEX,2*i-1..2*i),i=1..16)]): expandedKey := keyExpander(keyList): ListCipher := [seq(substring(cipherText,2*i-1..2*i), i = 1..16)]; cipherByteMatrix := map(hexTo8Bits,listToMatrix2(ListCipher)); plain := cipherByteMatrix; plain := InvBS(InvSR(ARK(plain, expandedKey,10))); for i from 1 to 9 do plain := InvBS(InvSR(InvMC(ARK(plain,expandedKey,10-i)))); end do; decryptMatrix := ARK(plain, expandedKey, 0); matrixToHex(decryptMatrix); end: |
Examples:
| > | cipherText := encryptAEShex("01020304050607080910111213141516", "0123456789ABCDEFFEDCBA9876543210"); decryptAEShex(cipherText, "0123456789ABCDEFFEDCBA9876543210"); |
| > | messageHex := "00000000000000000000000000000000": keyHex := "00000000000000000000000000000000": cipherText := encryptAEShex(messageHex, keyHex); decryptAEShex(cipherText, keyHex); |
| > | messageHex := "00000000000000000000000000000001": keyHex := "00000000000000000000000000000000": cipherText := encryptAEShex(messageHex, keyHex); decryptAEShex(cipherText, keyHex); |
| > | messageHex := "00000000000000000000000000000000": keyHex := "10000000000000000000000000000000": cipherText := encryptAEShex(messageHex, keyHex); decryptAEShex(cipherText, keyHex); |
| > |
In a standard encryption situation, we will want to encrypt many plaintext words with the same key. It that case it makes sense to expand the key once and use the expanded key in the procedure.
| > | hexKeyExpander := heyKey -> keyExpander(map(hexTo8Bits, [seq(substring(keyHex,2*i-1..2*i),i=1..16)])): messExpander := messageHEX -> listToMatrix2(map(hexTo8Bits, [seq(substring(messageHEX,2*i-1..2*i),i=1..16)])): encryptAESExpanded := proc(messHex, expandedKey) local cipher, i: cipher := ARK(messExpander(messHex), expandedKey, 0): for i from 1 to 9 do cipher := ARK(MC(SR(BS(cipher))),expandedKey,i): end do: matrixToHex(ARK(SR(BS(cipher)),expandedKey,10)): end: decryptAESExpanded := proc(cipherHex, expandedKey) local plain, i,decryptMatrix, keyList: plain := messExpander(cipherHex); plain := InvBS(InvSR(ARK(plain, expandedKey,10))); for i from 1 to 9 do plain := InvBS(InvSR(InvMC(ARK(plain,expandedKey,10-i)))); end do; decryptMatrix := ARK(plain, expandedKey, 0); matrixToHex(decryptMatrix); end: |
Examples:
| > | messageHex := "01020304050607080910111213141516": keyHex := "0123456789ABCDEFFEDCBA9876543210": expandedKey := hexKeyExpander(hexKey): message1Hex := "01020304050607080910111213141516": message2Hex := "00000000000000000000000000000000": message3Hex := "0123456789ABCDEFFEDCBA9876543210": cipher1Text := encryptAESExpanded(message1Hex, expandedKey); cipher2Text := encryptAESExpanded(message2Hex, expandedKey); cipher3Text := encryptAESExpanded(message3Hex, expandedKey); decryptAESExpanded(cipher1Text, expandedKey); decryptAESExpanded(cipher2Text, expandedKey); decryptAESExpanded(cipher3Text, expandedKey); |
| > |
ASCII messages and Byte List keys
These procedures can also be modified slightly so that the message is given in ASCII and the key is given as a list of bytes in binary format.
| > | encryptAESascii := proc(message, keyList) local expandedKey, cipher, cipherHexList, cipherHex, i,messMatrix: expandedKey := keyExpander(keyList): messMatrix := listToMatrix2(map(intToBits, convert(message,bytes))): cipher := ARK(messMatrix, expandedKey, 0): for i from 1 to 9 do cipher := ARK(MC(SR(BS(cipher))),expandedKey,i): end do: cipher := ARK(SR(BS(cipher)),expandedKey,10): cipherHexList := matrixToList2( map(x -> intToHex(bitToInt(x)),cipher)): cipherHex := cat(seq(cipherHexList[i],i=1..16)): end: |
| > | decryptAESascii := proc(cipherText, keyList) local expandedKey, ListCipher, cipherByteMatrix, plain, i,decryptMatrix, decryptList: expandedKey := keyExpander(keyList): ListCipher := [seq(substring(cipherText,2*i-1..2*i), i = 1..16)]; cipherByteMatrix := map(hexTo8Bits,listToMatrix2(ListCipher)); plain := cipherByteMatrix; plain := InvBS(InvSR(ARK(plain, expandedKey,10))); for i from 1 to 9 do plain := InvBS(InvSR(InvMC(ARK(plain,expandedKey,10-i)))); end do; decryptMatrix := ARK(plain, expandedKey, 0); decryptList := matrixToList2(map(bitToInt,decryptMatrix)); convert(decryptList,bytes); end: |
Example:
| > | mess1 := "Have a nice day."; testKeyHex := ["01","23","45","67","89","AB","CD","EF", "01","23","45","67","89","AB","CD","EF"]: testKey := map(hexTo8Bits,testKeyHex): cipherText := encryptAESascii(mess1, testKey); decryptAESascii(cipherText, testKey); |
Saving commands for future use
We want to save the commands and constants created here so that we can call them up for other work with AES without having to repeat all that we have done here.
We also add in some technical commands that we use for testing plaintexts with one nonzero bit against encrypted with a key of all zeroes,
The created file is called `AES.m` and will be in the current directory.
| > | intTo128Bits := intVal -> substring(convert(convert(2^128+intVal,hex),string),2..33): testline := proc(intVal) local mess1, cipher1, keyHex: keyHex := intTo128Bits(0): mess1 := intTo128Bits(2^intVal): cipher1 := encryptAEShex(mess1,keyHex); print(mess1, cipher1): end: testline2 := proc(intValMess, intValKey) local mess1, cipher1, keyHex: keyHex := intTo128Bits(intValKey): mess1 := intTo128Bits(intValMess): cipher1 := encryptAEShex(mess1,keyHex); print(mess1, cipher1): end: |
| > | save intToBits, bitToList, listToPoly, polyToInt, hexTo8Bits, listToBits, bitToInt, listToInt, listToInt, polyToList, intToHex, polyToBits, bitToPoly, intToPoly, listToMatrix, listToMatrix2, matrixToList, matrixToList2, matrixToHex, genPoly, MixMat, InvMixMat, XOR, xorNbits, xor8, SBoxTable, InvSBoxTable, roundFudge, polyToInt, roundFudgeWord, randKeyGenerator, keyExpander, BS, InvBS, SR, InvSR, MC, InvMC, ARK, encryptAESascii, decryptAESascii, encryptAEShex, decryptAEShex, intTo128Bits, testline, testline2, hexKeyExpander, messExpander, encryptAESExpanded, decryptAESExpanded, `AES.m`: |
| > |