hoschi@gfuzz:~$

AES (1/3): An Introduction to Galois Fields

Progression can only be achieved by something you have never done before.

Unlike DES, which is a Feistel cipher, AES is using substitution-permutation networks. These substitution permutations happen within Finite Fields, so called Galois Fields, in honour of the French mathematician Évariste Galois. Before we start going deeper into the algorithm, I want to explain the basic concepts behind Finite Fields arithmetic.

Modulo and Ring

Modulo means the remainder of integer division:

    10 / 6 = 1 remainder 4 or 10 % 6 = 4
     3 / 2 = 1 remainder 1 or  3 % 2 = 1

Congruent modulo means that two numbers divided by the same number have the same remainder, i.e.

    10 % 6 = 4
    16 % 6 = 4
     9 % 6 = 3
    15 % 6 = 3
Doing this calculation for a fixed number of Elements n of an subset of intergers gives us a ring of integer modulo n, n > 0
\[\mathbb{Z}/n\mathbb{Z}=\left \{ \bar{a}_{n} \mid a \in \mathbb{Z} \right \} = \left \{ 0_{n}, 1_{n}, 2_{n}, 3_{n}, ..., n - 1_{n} \right \}\]

Modulo 6 gives us the ring { 0, 1, 2, 3, 4, 5 }. The clue here is that each number, a so called “Restklasse”, represents all numbers in Z.

     0   1   2   3   4   5  (Restklasse)
 ----------------------------------------------
     6   7   8   9  10  11  
    12  13  14  15  16  17
    18  19  20  21  22  23
    24  25  26  27  28  29
    ..  ..  ..  ..  ..  ..  (...)

Group and Field

Since we want to do some actual calculations with our numbers we have to define a algebraic structure in which we can run mathematical operations. Starting with it’s simplest form, a group, which has the following defintions:

  • A group G is a set of elements.
  • “o” in this context here means mathematical operation
  • An operation o combines two elements a and b within group G
  • The operation is closed, which means that both a and b are elements of G, and also c, which is a combined with b, is an element of G
  • The operation is associative, so (a o b) o c = a o (b o c) where a,b,c element of G
  • There is a neutral element 1 in G, where a o 1 = 1 o a = a, 1, a element G
  • Every element in G has a so called inverse element a^-1 which combined with a has the neutral element 1 as the result a o a^-1 = a^-1 o a = 1, a, a^-1, 1 element G
  • Group G is communitative, which means that a o b = b o a, a, b element G

If the mathematical operation o is addition, it’s inverse is substraction, if the operation is multiplication, it’s inverse is division OR multiplication by it’s inverse element. Joining all operations together forms a new structure: a field.

A set of elements in a field F has the following properties:

  • The + operation forms an additive group with the neutral element of 0 (a + 0 = a, 0, a element F)
  • The * operations forms a multiplicative group with the neutral element of 1 (a * 1 = a, 1, a element F)
  • Combining both operations above follow the distribution law where a * (b + c) = ab + ac, a,b,c element F

Prime Fields and Extension Fields

A very special occurence of a field exists when it’s characteristic happen to be a prime number p. A field over the prime number p is called a “Prime Field”, i.e.

F(2), F(5), F(7), ... where F(2) is the smallest Prime Field that exists.

If the elements of a field can be factorized and expressed as p^m, where p is a prime and m is a positive integer, the field happens to be an “Extension Field” (or Galois Field), i.e.

F(81) => F(3^4), F(8) => F(2^3) or even better GF(256) => GF(2^8)

GF(2^m) is probably the most interesting finite field in computer science since it’s elements { 0, 1 } are perfect for bitwise representation and are not only used for cryptography but also for checksumming and error correction in data transmission. In contrast to Prime Fields, where the elements are simply the numbers form 0 to p-1, Extension Fields are a set of polynomials, a good example of the elements and their bitwise representation is GF(2^3) (8 Elements with 3 bits size).

    0 0 0             0
    0 0 1             1
    0 1 0         x
    0 1 1         x + 1
    1 0 0   x^2
    1 0 1   x^2     + 1
    1 1 0   x^2 + x
    1 1 1   x^2 + x + 1
Within AES, most of the calculations are done within the Galois Field 2^8 (256 elements), it’s polynomial expression is from degreee 7
\[a8 * x^7 + a7 * x^6 + a6 * x^5 + a5 * x^4 + a4 * x^3 + a2 * x^1 * + a1 * x^0\]

It probably looks more complicated than it actually is: the formula above is basically is the bit range 0000 0000 to 1111 1111, a so called byte. Each x^n represents a single bit at position n.

Addition, multiplication and Inversion

Prime Field definitions

  • p is a prime, a Galois Field is GF(p) and has p elements
  • Non-Zero elements of GF(p) have an inverse element, which are also in GF(p)
  • Modulo p reduction is used in all GF(p) arithmetic For example, we are now able to create a full example of a prime field GF(7):
    Additive chart              Additive inverse
    0 1 2 3 4 5 6         
    0 0 1 2 3 4 5 6             -0 = 0
    1 1 2 3 4 5 6 0             -1 = 6
    2 2 3 4 5 6 0 1             -2 = 5
    3 3 4 5 6 0 1 2             -3 = 4
    4 4 5 6 0 1 2 3             -4 = 3
    5 5 6 0 1 2 3 4             -5 = 2
    6 6 0 1 2 3 4 5             -6 = 1
    Multiplicative chart        Multiplicative inverse
    0 1 2 3 4 5 6
    0 0 0 0 0 0 0 0             0^(-1) = n/a
    1 0 1 2 3 4 5 6             1^(-1) = 1
    2 0 2 4 6 1 3 5             2^(-1) = 4
    3 0 3 6 2 5 1 4             3^(-1) = 5
    4 0 4 1 5 2 6 3             4^(-1) = 2
    5 0 5 3 1 6 4 2             5^(-1) = 3
    6 0 6 5 4 3 2 1             6^(-1) = 6
    

    Since we want to focus on GF(2) and the corresponding Extension Field GF(2^8), the table look like:

    Additive chart              Additive inverse
    0 1                       
    0 0 1                       -0 = 0
    1 1 0                       -1 = 1
    

    Multiplicative chart Multiplicative inverse 0 1 0 0 0 0^(-1) = n/a 1 0 1 1^(-1) = 1 ``` In our implementation, GF(p) addition can be seen as XOR (exclusive or) and multiplication in GF(p) as AND (logical and). We now know that a + b = c mod p, a, b, c elements in GF(p) and a * b = c mod p, a,b,c elements GF(p). Arithmetic in GF(2^m) is a bit different. Tough, addition can still be implemented using XOR. However, multiplication behaves very different.

Recalling our example GF(2^3) we know that the resulting 8 elements are made of polynomials:
\[GF(2^3) = \left \{ 0, 1, x, x + 1, x^2, x^2 + 1, x² + x, x^2 + x + 1 \right \}\]
For that purpose we have to introduce polynomial division in GF(2^m). Like a prime number, a polynomial can also be irreducible, we just have to choose a polynomial of degree which our irreducible polynomials of degree 3 are:
\[P(x) = x^3 + x + 1\]
\[P(x) = x^3 + x^2 + 1\]

From now on we will focus on arithmetic in GF(2^m), mainly GF(2^8), and the implementation in the C programming language.

Multiplication in the AES field

Multiplication in GF(2^8), the so called AES field, can be described as the following:
\[A(x) * B(x) = C(x) \mod P(x)\]
(where P is an irreducibly polynomial of the degree 8 ). Obviously, the selection of the irreducible polynomial has an impact on the result, the standard defines the following polynomial to be used, also known as the AES polynomial:
\[P(x) = x^8 + x^4 + x^3 + x + 1\]

The corresponding binary representation is 100011011 or 0x11b in hexadecimal.

In section 4.2 Multiplication of the AES standard we can find an example which states: Hexadecimal notation:

0x53 * 0x83 = 0xc1
Polynomial notation:
\[(x^6 + x^4 + x^2 + x + 1) * (x^7 + x + 1) = x^7 + x^6 + 1\]

Binary notation:

0101 0111 * 1000 0011 = 1100 0001

My implementation works on a binary level, so we will use shifts with logical AND and XOR operations:

for(int i = 0; i < 8; i++)
{
    if(((factor2 >> i) & 0x1) == 1)
    {
        uint16_t intermediate = factor1;
        result ^= (intermediate <<= i);
    }
}

We take each set bit of the 2nd factor, use it’s position i and with that, we left shift the 1st factor by i position(s) and XOR the intermediate result into a final result.

                    7654 3210     
            (0x83)  1000 0011
(0x53)  0101 0111 * 1           = 010 1011 1000 0000    (left shift by 7)
        0101 0111 *        1    =        0 1010 1110    (left shift by 1)
        0101 0111 *         1   =          0101 0111    (no shift)
                              XOR 010 1011 0111 1001
The result (0x2B79) is not an element in our Finite Field, we have to use modulo p reduction, polynomial notation is:
\[(x^{13} + x^{11} + x^9 + x^8 + x^6 + x^5 + x^4 + x^3 + 1) \mod (x^8 + x^4 + x^3 + x + 1)\]

In C, this can be implemented like this (NB: get_shifts() returns the position of the left most set bit of an 8bit value)

uint16_t result = *factor;
int8_t shift = get_shifts(&result);
while (shift < 8 && shift >= 0)
{
    result ^= (AES_POLYNOMIAL << (7 - shift));
    shift = get_shifts(&result);
} 

Reduce our factor by AES_POLYNOMIAL until it fits in 8 bit again. Mod p reduction is done using logical XOR operation by a n-left-shifted AES_POLYNOMIAL.

10101101111001
100011011      XOR (irreducible AES polynomial, left shifted by 5)
  100000011001
  100011011    XOR (left shifted by 3)
      11000001 which is 0xc1 in hexadecimal 

We have shown that 0x53 * 0x83 = 0x2B79 mod 0x11b = 0xc1.

Field Generators

If you take an element n in GF(2^8) and multiply it by itself (including modulo p reduction) for 2^8 times you create the multiplicative group, a good example might be the element 2 (we use decimal representation here):

2   4   8  16  32  64 128  27  54 108 216 171  77 154  47  94 
188  99 198 151  53 106 212 179 125 250 239 197 145  57 114 228 
211 189  97 194 159  37  74 148  51 102 204 131  29  58 116 232 
203 141   1   2   4   8  16  32  64 128  27  54 108 216 171  77 
154  47  94 188  99 198 151  53 106 212 179 125 250 239 197 145 
 57 114 228 211 189  97 194 159  37  74 148  51 102 204 131  29 
 58 116 232 203 141   1   2   4   8  16  32  64 128  27  54 108 
216 171  77 154  47  94 188  99 198 151  53 106 212 179 125 250 
239 197 145  57 114 228 211 189  97 194 159  37  74 148  51 102 
204 131  29  58 116 232 203 141   1   2   4   8  16  32  64 128 
 27  54 108 216 171  77 154  47  94 188  99 198 151  53 106 212 
179 125 250 239 197 145  57 114 228 211 189  97 194 159  37  74 
148  51 102 204 131  29  58 116 232 203 141   1   2   4   8  16 
 32  64 128  27  54 108 216 171  77 154  47  94 188  99 198 151 
 53 106 212 179 125 250 239 197 145  57 114 228 211 189  97 194 
159  37  74 148  51 102 204 131  29  58 116 232 203 141   1   2 

As you can see, the multiplicative group is cyclic. For AES, we need a multiplicative group which has unique elements between the first and last multiplication, a so called Generator. An C example of a brute force method to calculate all valid generators under GF(2^8) can be found here. A full list of all multiplicative groups can be found here.

However, only 128 of the multiplicative groups match the characterisitic of a Generator, these are the following (decimal representation):

  3   5   6   9  11  14  17  18  19  20  23  24  25  26  28  30 
 31  33  34  35  39  40  42  44  48  49  60  62  63  65  69  70 
 71  72  73  75  76  78  79  82  84  86  87  88  89  90  91  95 
100 101 104 105 109 110 112 113 118 119 121 122 123 126 129 132 
134 135 136 138 142 143 144 147 149 150 152 153 155 157 160 164 
165 166 167 169 170 172 173 178 180 183 184 185 186 190 191 192 
193 196 200 201 206 207 208 214 215 218 220 221 222 226 227 229 
230 231 233 234 235 238 240 241 244 245 246 248 251 253 254 255 

We will use the smallest Generator within the AES field and the following multiplicative group, all in hexadecimal representation: Generator: 0x03

0x03 0x05 0x0f 0x11 0x33 0x55 0xff 0x1a 0x2e 0x72 0x96 0xa1 0xf8 0x13 0x35 0x5f 
0xe1 0x38 0x48 0xd8 0x73 0x95 0xa4 0xf7 0x02 0x06 0x0a 0x1e 0x22 0x66 0xaa 0xe5 
0x34 0x5c 0xe4 0x37 0x59 0xeb 0x26 0x6a 0xbe 0xd9 0x70 0x90 0xab 0xe6 0x31 0x53 
0xf5 0x04 0x0c 0x14 0x3c 0x44 0xcc 0x4f 0xd1 0x68 0xb8 0xd3 0x6e 0xb2 0xcd 0x4c 
0xd4 0x67 0xa9 0xe0 0x3b 0x4d 0xd7 0x62 0xa6 0xf1 0x08 0x18 0x28 0x78 0x88 0x83 
0x9e 0xb9 0xd0 0x6b 0xbd 0xdc 0x7f 0x81 0x98 0xb3 0xce 0x49 0xdb 0x76 0x9a 0xb5 
0xc4 0x57 0xf9 0x10 0x30 0x50 0xf0 0x0b 0x1d 0x27 0x69 0xbb 0xd6 0x61 0xa3 0xfe 
0x19 0x2b 0x7d 0x87 0x92 0xad 0xec 0x2f 0x71 0x93 0xae 0xe9 0x20 0x60 0xa0 0xfb 
0x16 0x3a 0x4e 0xd2 0x6d 0xb7 0xc2 0x5d 0xe7 0x32 0x56 0xfa 0x15 0x3f 0x41 0xc3 
0x5e 0xe2 0x3d 0x47 0xc9 0x40 0xc0 0x5b 0xed 0x2c 0x74 0x9c 0xbf 0xda 0x75 0x9f 
0xba 0xd5 0x64 0xac 0xef 0x2a 0x7e 0x82 0x9d 0xbc 0xdf 0x7a 0x8e 0x89 0x80 0x9b 
0xb6 0xc1 0x58 0xe8 0x23 0x65 0xaf 0xea 0x25 0x6f 0xb1 0xc8 0x43 0xc5 0x54 0xfc 
0x1f 0x21 0x63 0xa5 0xf4 0x07 0x09 0x1b 0x2d 0x77 0x99 0xb0 0xcb 0x46 0xca 0x45 
0xcf 0x4a 0xde 0x79 0x8b 0x86 0x91 0xa8 0xe3 0x3e 0x42 0xc6 0x51 0xf3 0x0e 0x12 
0x36 0x5a 0xee 0x29 0x7b 0x8d 0x8c 0x8f 0x8a 0x85 0x94 0xa7 0xf2 0x0d 0x17 0x39 
0x4b 0xdd 0x7c 0x84 0x97 0xa2 0xfd 0x1c 0x24 0x6c 0xb4 0xc7 0x52 0xf6 0x01 0x03 

Multiplicative inverse The calculations and lookups within the multiplicative inverse group one of the core parts of the AES algorithm. It is calculated using the exponentation and logarithm charts of a multiplicative group. To calculate an exponentation chart we take each 2^8 elements to the power of our generator 0x03, this can done using the following loop:

uint16_t exp = 1;
for(int i = 1; i <= 256; i++)
{
    exp = multiply_polynomial(exp, 0x03);
    exp = aes_polynomial_division(exp);
    printf("0x%02x ", poly);
    if(i % 16 == 0)
        puts("");
}

The output is the following:

0x01 0x03 0x05 0x0f 0x11 0x33 0x55 0xff 0x1a 0x2e 0x72 0x96 0xa1 0xf8 0x13 0x35 
0x5f 0xe1 0x38 0x48 0xd8 0x73 0x95 0xa4 0xf7 0x02 0x06 0x0a 0x1e 0x22 0x66 0xaa 
0xe5 0x34 0x5c 0xe4 0x37 0x59 0xeb 0x26 0x6a 0xbe 0xd9 0x70 0x90 0xab 0xe6 0x31 
0x53 0xf5 0x04 0x0c 0x14 0x3c 0x44 0xcc 0x4f 0xd1 0x68 0xb8 0xd3 0x6e 0xb2 0xcd 
0x4c 0xd4 0x67 0xa9 0xe0 0x3b 0x4d 0xd7 0x62 0xa6 0xf1 0x08 0x18 0x28 0x78 0x88 
0x83 0x9e 0xb9 0xd0 0x6b 0xbd 0xdc 0x7f 0x81 0x98 0xb3 0xce 0x49 0xdb 0x76 0x9a 
0xb5 0xc4 0x57 0xf9 0x10 0x30 0x50 0xf0 0x0b 0x1d 0x27 0x69 0xbb 0xd6 0x61 0xa3 
0xfe 0x19 0x2b 0x7d 0x87 0x92 0xad 0xec 0x2f 0x71 0x93 0xae 0xe9 0x20 0x60 0xa0 
0xfb 0x16 0x3a 0x4e 0xd2 0x6d 0xb7 0xc2 0x5d 0xe7 0x32 0x56 0xfa 0x15 0x3f 0x41 
0xc3 0x5e 0xe2 0x3d 0x47 0xc9 0x40 0xc0 0x5b 0xed 0x2c 0x74 0x9c 0xbf 0xda 0x75 
0x9f 0xba 0xd5 0x64 0xac 0xef 0x2a 0x7e 0x82 0x9d 0xbc 0xdf 0x7a 0x8e 0x89 0x80 
0x9b 0xb6 0xc1 0x58 0xe8 0x23 0x65 0xaf 0xea 0x25 0x6f 0xb1 0xc8 0x43 0xc5 0x54 
0xfc 0x1f 0x21 0x63 0xa5 0xf4 0x07 0x09 0x1b 0x2d 0x77 0x99 0xb0 0xcb 0x46 0xca 
0x45 0xcf 0x4a 0xde 0x79 0x8b 0x86 0x91 0xa8 0xe3 0x3e 0x42 0xc6 0x51 0xf3 0x0e 
0x12 0x36 0x5a 0xee 0x29 0x7b 0x8d 0x8c 0x8f 0x8a 0x85 0x94 0xa7 0xf2 0x0d 0x17 
0x39 0x4b 0xdd 0x7c 0x84 0x97 0xa2 0xfd 0x1c 0x24 0x6c 0xb4 0xc7 0x52 0xf6 0x01 

Going one step further, with the calculation above we can easily derive the logarithm chart:

exp = 1;
uint8_t logarithm[16][16] = { 0 };  // 0xF * 0xF Elements
for(int i = 1; i < 255; i++)
{
    exp = multiply_polynomial(poly, GENERATOR);
    exp = aes_polynomial_division(poly);

    uint8_t x = exp & 0xF;
    uint8_t y = exp >> 4;
    logarithm[y][x] = i;
}

We take each element of the exponentation chart and store it into our logarithm table where the 4 left most bits indicate the y axis and the 4 right most bits the x axis.

The logarithm chart for 0x03 (NB: 0x00 is not defined):

     0x00 0x19 0x01 0x32 0x02 0x1a 0xc6 0x4b 0xc7 0x1b 0x68 0x33 0xee 0xdf 0x03 
0x64 0x04 0xe0 0x0e 0x34 0x8d 0x81 0xef 0x4c 0x71 0x08 0xc8 0xf8 0x69 0x1c 0xc1 
0x7d 0xc2 0x1d 0xb5 0xf9 0xb9 0x27 0x6a 0x4d 0xe4 0xa6 0x72 0x9a 0xc9 0x09 0x78 
0x65 0x2f 0x8a 0x05 0x21 0x0f 0xe1 0x24 0x12 0xf0 0x82 0x45 0x35 0x93 0xda 0x8e 
0x96 0x8f 0xdb 0xbd 0x36 0xd0 0xce 0x94 0x13 0x5c 0xd2 0xf1 0x40 0x46 0x83 0x38 
0x66 0xdd 0xfd 0x30 0xbf 0x06 0x8b 0x62 0xb3 0x25 0xe2 0x98 0x22 0x88 0x91 0x10 
0x7e 0x6e 0x48 0xc3 0xa3 0xb6 0x1e 0x42 0x3a 0x6b 0x28 0x54 0xfa 0x85 0x3d 0xba 
0x2b 0x79 0x0a 0x15 0x9b 0x9f 0x5e 0xca 0x4e 0xd4 0xac 0xe5 0xf3 0x73 0xa7 0x57 
0xaf 0x58 0xa8 0x50 0xf4 0xea 0xd6 0x74 0x4f 0xae 0xe9 0xd5 0xe7 0xe6 0xad 0xe8 
0x2c 0xd7 0x75 0x7a 0xeb 0x16 0x0b 0xf5 0x59 0xcb 0x5f 0xb0 0x9c 0xa9 0x51 0xa0 
0x7f 0x0c 0xf6 0x6f 0x17 0xc4 0x49 0xec 0xd8 0x43 0x1f 0x2d 0xa4 0x76 0x7b 0xb7 
0xcc 0xbb 0x3e 0x5a 0xfb 0x60 0xb1 0x86 0x3b 0x52 0xa1 0x6c 0xaa 0x55 0x29 0x9d 
0x97 0xb2 0x87 0x90 0x61 0xbe 0xdc 0xfc 0xbc 0x95 0xcf 0xcd 0x37 0x3f 0x5b 0xd1 
0x53 0x39 0x84 0x3c 0x41 0xa2 0x6d 0x47 0x14 0x2a 0x9e 0x5d 0x56 0xf2 0xd3 0xab 
0x44 0x11 0x92 0xd9 0x23 0x20 0x2e 0x89 0xb4 0x7c 0xb8 0x26 0x77 0x99 0xe3 0xa5 
0x67 0x4a 0xed 0xde 0xc5 0x31 0xfe 0x18 0x0d 0x63 0x8c 0x80 0xc0 0xf7 0x70 0x07 

Now we are only one step away from calculating the multiplicative inverse group. Take each element of 2^8, find it’s logarithm and inverse it by adding 0xFF (XOR). Using that result, we create a new exponentation chart using our generator 0x03:

for(int i = 0; i < 256; i++)
{
    if(i % 16 == 0 && i > 0)
        puts("");
    if(i == 0)
    {
        printf("-- ");
        continue;
    }
    uint8_t x = i & 0xF;
    uint8_t y = i >> 4;
    uint8_t k = logarithm[y][x];
    k ^= 0xFF;  // substract 255 in GF(2^8)
    uint16_t exp = 1;
    for(int j = 0; j < k; j++)
    {
        exp = multiply_polynomial(exp, 0x03);
        exp = aes_polynomial_division(exp);
    }
    printf("0x%02x ", exp);
}

The multiplicative inverse group in GF(2^8) (NB: 0x00 is not defined):

     0x01 0x8d 0xf6 0xcb 0x52 0x7b 0xd1 0xe8 0x4f 0x29 0xc0 0xb0 0xe1 0xe5 0xc7 
0x74 0xb4 0xaa 0x4b 0x99 0x2b 0x60 0x5f 0x58 0x3f 0xfd 0xcc 0xff 0x40 0xee 0xb2 
0x3a 0x6e 0x5a 0xf1 0x55 0x4d 0xa8 0xc9 0xc1 0x0a 0x98 0x15 0x30 0x44 0xa2 0xc2 
0x2c 0x45 0x92 0x6c 0xf3 0x39 0x66 0x42 0xf2 0x35 0x20 0x6f 0x77 0xbb 0x59 0x19 
0x1d 0xfe 0x37 0x67 0x2d 0x31 0xf5 0x69 0xa7 0x64 0xab 0x13 0x54 0x25 0xe9 0x09 
0xed 0x5c 0x05 0xca 0x4c 0x24 0x87 0xbf 0x18 0x3e 0x22 0xf0 0x51 0xec 0x61 0x17 
0x16 0x5e 0xaf 0xd3 0x49 0xa6 0x36 0x43 0xf4 0x47 0x91 0xdf 0x33 0x93 0x21 0x3b 
0x79 0xb7 0x97 0x85 0x10 0xb5 0xba 0x3c 0xb6 0x70 0xd0 0x06 0xa1 0xfa 0x81 0x82 
0x83 0x7e 0x7f 0x80 0x96 0x73 0xbe 0x56 0x9b 0x9e 0x95 0xd9 0xf7 0x02 0xb9 0xa4 
0xde 0x6a 0x32 0x6d 0xd8 0x8a 0x84 0x72 0x2a 0x14 0x9f 0x88 0xf9 0xdc 0x89 0x9a 
0xfb 0x7c 0x2e 0xc3 0x8f 0xb8 0x65 0x48 0x26 0xc8 0x12 0x4a 0xce 0xe7 0xd2 0x62 
0x0c 0xe0 0x1f 0xef 0x11 0x75 0x78 0x71 0xa5 0x8e 0x76 0x3d 0xbd 0xbc 0x86 0x57 
0x0b 0x28 0x2f 0xa3 0xda 0xd4 0xe4 0x0f 0xa9 0x27 0x53 0x04 0x1b 0xfc 0xac 0xe6 
0x7a 0x07 0xae 0x63 0xc5 0xdb 0xe2 0xea 0x94 0x8b 0xc4 0xd5 0x9d 0xf8 0x90 0x6b 
0xb1 0x0d 0xd6 0xeb 0xc6 0x0e 0xcf 0xad 0x08 0x4e 0xd7 0xe3 0x5d 0x50 0x1e 0xb3 
0x5b 0x23 0x38 0x34 0x68 0x46 0x03 0x8c 0xdd 0x9c 0x7d 0xa0 0xcd 0x1a 0x41 0x1c 

While each generator in GF(2^8) results in different exponentation and logarithm charts, the multiplicative group is always the same. All we have to do is choose a valid generator and run the functions above.

Go to next article: A Description of AES Lookup Tables