I talked about Rijndael in a couple of previous posts: Sieving irreducible monic polynomials over a finite field, Addition and multiplication table for GF(2²)

I’m going to talk some more about it today.

Rijndael does a lot of arithmetic operations (addition and multiplication) on elements of GF(2^{8})^{4}.

These are represented as polynomials *b*_{0} + *b*_{1} *x* + *b*_{2} *x*^{2} + *b*_{3} *x*^{3}.

The individual coefficients *b*_{i} are elements of GF(2^{8}).

These are themselves polynomials *a*_{0} + *a*_{1} *x* + … + *a*_{7} *x*^{7} where the individual coefficients *a*_{i} are bits (0 or 1).

Multiplication *b*_{i} *b*_{j} is made into a closed operation by taking the modulus of the product under the reduction polynomial *m* = *x*^{8} + *x*^{4} + *x*^{3} + *x* + 1.

The reduction polynomial is prime, which is important in establishing that this representation of the Galois field really is a field.

For convenience we will represent the elements of GF(2^{8}) as hexadecimal numbers **0x00**, **0x01**, … **0xfe**, **0xff**.

By contrast, we will represent the elements of GF(2^{8})^{4} as vectors (*b*_{0} *b*_{1} *b*_{2} *b*_{3}); for example, (**0x00** **0x01** **0x02** **0x03**).

Multiplying vectors in GF(2^{8})^{4} induces a bunch of multiplications in GF(2^{8}). It would be good to come up with a way to make this fast. Doing a polynomial reduction every time is not fast.

Of course, one way to do this is to create a multiplication table with 256 rows and 256 columns, do each multiplication the slow way once, and compile in the results. This would require on the order of 64 kB. But there’s a better way.

The trick that is used in Daemen and Rijmen’s implementation is to find a primitive element *e* in GF(2^{8}) so that the *orbit* of *e* (that is to say, the set {*e*^{0}, *e*^{1}, …, *e*^{254}}) spans all of GF(2^{8}) – {**0x00**}.

If we find such an *e*, then we can save a cheat sheet which is almost as fast as the multiplication table, but requires storing only on the order of 256 bytes.

Well, let’s calculate the orbits of all the elements in order until we find a primitive element.

Orbit(**0x00**) = { **0x00**^{0}, **0x00**^{1}, **0x00**^{2}, … } = { **0x01**, **0x00**, **0x00**, … } = { **0x00**, **0x01** }. Nope.

Orbit(**0x01**) = { **0x01**^{0}, **0x01**^{1}, **0x01**^{2}, … } = { **0x01**, **0x01**, **0x01**, … } = { **0x01** }. Even worse.

Orbit(**0x02**) = { **0x02**^{0}, **0x02**^{1}, **0x02**^{2}, … } = { **0x01**, **0x02**, **0x04**, …, **0x8d** }. This looks promising at first but we end up with an orbit of only 51 out of the necessary 255 elements (note that 51 divides 255:)

**0x02**^{0} = **0x01**

**0x02**^{1} = **0x02**

**0x02**^{2} = **0x04**

**0x02**^{3} = **0x08**

**0x02**^{4} = **0x10**

**0x02**^{5} = **0x20**

**0x02**^{6} = **0x40**

**0x02**^{7} = **0x80**

**0x02**^{8} = **0x1b**

**0x02**^{9} = **0x36**

**0x02**^{10} = **0x6c**

**0x02**^{11} = **0xd8**

**0x02**^{12} = **0xab**

**0x02**^{13} = **0x4d**

**0x02**^{14} = **0x9a**

**0x02**^{15} = **0x2f**

**0x02**^{16} = **0x5e**

**0x02**^{17} = **0xbc**

**0x02**^{18} = **0x63**

**0x02**^{19} = **0xc6**

**0x02**^{20} = **0x97**

**0x02**^{21} = **0x35**

**0x02**^{22} = **0x6a**

**0x02**^{23} = **0xd4**

**0x02**^{24} = **0xb3**

**0x02**^{25} = **0x7d**

**0x02**^{26} = **0xfa**

**0x02**^{27} = **0xef**

**0x02**^{28} = **0xc5**

**0x02**^{29} = **0x91**

**0x02**^{30} = **0x39**

**0x02**^{31} = **0x72**

**0x02**^{32} = **0xe4**

**0x02**^{33} = **0xd3**

**0x02**^{34} = **0xbd**

**0x02**^{35} = **0x61**

**0x02**^{36} = **0xc2**

**0x02**^{37} = **0x9f**

**0x02**^{38} = **0x25**

**0x02**^{39} = **0x4a**

**0x02**^{40} = **0x94**

**0x02**^{41} = **0x33**

**0x02**^{42} = **0x66**

**0x02**^{43} = **0xcc**

**0x02**^{44} = **0x83**

**0x02**^{45} = **0x1d**

**0x02**^{46} = **0x3a**

**0x02**^{47} = **0x74**

**0x02**^{48} = **0xe8**

**0x02**^{49} = **0xcb**

**0x02**^{50} = **0x8d**

And then we circle back around to…

**0x02**^{51} = **0x01**

Well, let’s keep looking. What about **0x03**?

Orbit(**0x03**) = { **0x03**^{0}, **0x03**^{1}, **0x03**^{2}, … } = { **0x01**, **0x03**, **0x05**, …, **0xf6** }. Bingo, we hit all 255 non-**0x00** elements!

**0x03**^{0} = **0x01**

**0x03**^{1} = **0x03**

**0x03**^{2} = **0x05**

**0x03**^{3} = **0x0f**

**0x03**^{4} = **0x11**

**0x03**^{5} = **0x33**

**0x03**^{6} = **0x55**

**0x03**^{7} = **0xff**

**0x03**^{8} = **0x1a**

**0x03**^{9} = **0x2e**

**0x03**^{10} = **0x72**

**0x03**^{11} = **0x96**

**0x03**^{12} = **0xa1**

**0x03**^{13} = **0xf8**

**0x03**^{14} = **0x13**

**0x03**^{15} = **0x35**

**0x03**^{16} = **0x5f**

**0x03**^{17} = **0xe1**

**0x03**^{18} = **0x38**

**0x03**^{19} = **0x48**

**0x03**^{20} = **0xd8**

**0x03**^{21} = **0x73**

**0x03**^{22} = **0x95**

**0x03**^{23} = **0xa4**

**0x03**^{24} = **0xf7**

**0x03**^{25} = **0x02**

**0x03**^{26} = **0x06**

**0x03**^{27} = **0x0a**

**0x03**^{28} = **0x1e**

**0x03**^{29} = **0x22**

**0x03**^{30} = **0x66**

**0x03**^{31} = **0xaa**

**0x03**^{32} = **0xe5**

**0x03**^{33} = **0x34**

**0x03**^{34} = **0x5c**

**0x03**^{35} = **0xe4**

**0x03**^{36} = **0x37**

**0x03**^{37} = **0x59**

**0x03**^{38} = **0xeb**

**0x03**^{39} = **0x26**

**0x03**^{40} = **0x6a**

**0x03**^{41} = **0xbe**

**0x03**^{42} = **0xd9**

**0x03**^{43} = **0x70**

**0x03**^{44} = **0x90**

**0x03**^{45} = **0xab**

**0x03**^{46} = **0xe6**

**0x03**^{47} = **0x31**

**0x03**^{48} = **0x53**

**0x03**^{49} = **0xf5**

**0x03**^{50} = **0x04**

**0x03**^{51} = **0x0c**

**0x03**^{52} = **0x14**

**0x03**^{53} = **0x3c**

**0x03**^{54} = **0x44**

**0x03**^{55} = **0xcc**

**0x03**^{56} = **0x4f**

**0x03**^{57} = **0xd1**

**0x03**^{58} = **0x68**

**0x03**^{59} = **0xb8**

**0x03**^{60} = **0xd3**

**0x03**^{61} = **0x6e**

**0x03**^{62} = **0xb2**

**0x03**^{63} = **0xcd**

**0x03**^{64} = **0x4c**

**0x03**^{65} = **0xd4**

**0x03**^{66} = **0x67**

**0x03**^{67} = **0xa9**

**0x03**^{68} = **0xe0**

**0x03**^{69} = **0x3b**

**0x03**^{70} = **0x4d**

**0x03**^{71} = **0xd7**

**0x03**^{72} = **0x62**

**0x03**^{73} = **0xa6**

**0x03**^{74} = **0xf1**

**0x03**^{75} = **0x08**

**0x03**^{76} = **0x18**

**0x03**^{77} = **0x28**

**0x03**^{78} = **0x78**

**0x03**^{79} = **0x88**

**0x03**^{80} = **0x83**

**0x03**^{81} = **0x9e**

**0x03**^{82} = **0xb9**

**0x03**^{83} = **0xd0**

**0x03**^{84} = **0x6b**

**0x03**^{85} = **0xbd**

**0x03**^{86} = **0xdc**

**0x03**^{87} = **0x7f**

**0x03**^{88} = **0x81**

**0x03**^{89} = **0x98**

**0x03**^{90} = **0xb3**

**0x03**^{91} = **0xce**

**0x03**^{92} = **0x49**

**0x03**^{93} = **0xdb**

**0x03**^{94} = **0x76**

**0x03**^{95} = **0x9a**

**0x03**^{96} = **0xb5**

**0x03**^{97} = **0xc4**

**0x03**^{98} = **0x57**

**0x03**^{99} = **0xf9**

**0x03**^{100} = **0x10**

**0x03**^{101} = **0x30**

**0x03**^{102} = **0x50**

**0x03**^{103} = **0xf0**

**0x03**^{104} = **0x0b**

**0x03**^{105} = **0x1d**

**0x03**^{106} = **0x27**

**0x03**^{107} = **0x69**

**0x03**^{108} = **0xbb**

**0x03**^{109} = **0xd6**

**0x03**^{110} = **0x61**

**0x03**^{111} = **0xa3**

**0x03**^{112} = **0xfe**

**0x03**^{113} = **0x19**

**0x03**^{114} = **0x2b**

**0x03**^{115} = **0x7d**

**0x03**^{116} = **0x87**

**0x03**^{117} = **0x92**

**0x03**^{118} = **0xad**

**0x03**^{119} = **0xec**

**0x03**^{120} = **0x2f**

**0x03**^{121} = **0x71**

**0x03**^{122} = **0x93**

**0x03**^{123} = **0xae**

**0x03**^{124} = **0xe9**

**0x03**^{125} = **0x20**

**0x03**^{126} = **0x60**

**0x03**^{127} = **0xa0**

**0x03**^{128} = **0xfb**

**0x03**^{129} = **0x16**

**0x03**^{130} = **0x3a**

**0x03**^{131} = **0x4e**

**0x03**^{132} = **0xd2**

**0x03**^{133} = **0x6d**

**0x03**^{134} = **0xb7**

**0x03**^{135} = **0xc2**

**0x03**^{136} = **0x5d**

**0x03**^{137} = **0xe7**

**0x03**^{138} = **0x32**

**0x03**^{139} = **0x56**

**0x03**^{140} = **0xfa**

**0x03**^{141} = **0x15**

**0x03**^{142} = **0x3f**

**0x03**^{143} = **0x41**

**0x03**^{144} = **0xc3**

**0x03**^{145} = **0x5e**

**0x03**^{146} = **0xe2**

**0x03**^{147} = **0x3d**

**0x03**^{148} = **0x47**

**0x03**^{149} = **0xc9**

**0x03**^{150} = **0x40**

**0x03**^{151} = **0xc0**

**0x03**^{152} = **0x5b**

**0x03**^{153} = **0xed**

**0x03**^{154} = **0x2c**

**0x03**^{155} = **0x74**

**0x03**^{156} = **0x9c**

**0x03**^{157} = **0xbf**

**0x03**^{158} = **0xda**

**0x03**^{159} = **0x75**

**0x03**^{160} = **0x9f**

**0x03**^{161} = **0xba**

**0x03**^{162} = **0xd5**

**0x03**^{163} = **0x64**

**0x03**^{164} = **0xac**

**0x03**^{165} = **0xef**

**0x03**^{166} = **0x2a**

**0x03**^{167} = **0x7e**

**0x03**^{168} = **0x82**

**0x03**^{169} = **0x9d**

**0x03**^{170} = **0xbc**

**0x03**^{171} = **0xdf**

**0x03**^{172} = **0x7a**

**0x03**^{173} = **0x8e**

**0x03**^{174} = **0x89**

**0x03**^{175} = **0x80**

**0x03**^{176} = **0x9b**

**0x03**^{177} = **0xb6**

**0x03**^{178} = **0xc1**

**0x03**^{179} = **0x58**

**0x03**^{180} = **0xe8**

**0x03**^{181} = **0x23**

**0x03**^{182} = **0x65**

**0x03**^{183} = **0xaf**

**0x03**^{184} = **0xea**

**0x03**^{185} = **0x25**

**0x03**^{186} = **0x6f**

**0x03**^{187} = **0xb1**

**0x03**^{188} = **0xc8**

**0x03**^{189} = **0x43**

**0x03**^{190} = **0xc5**

**0x03**^{191} = **0x54**

**0x03**^{192} = **0xfc**

**0x03**^{193} = **0x1f**

**0x03**^{194} = **0x21**

**0x03**^{195} = **0x63**

**0x03**^{196} = **0xa5**

**0x03**^{197} = **0xf4**

**0x03**^{198} = **0x07**

**0x03**^{199} = **0x09**

**0x03**^{200} = **0x1b**

**0x03**^{201} = **0x2d**

**0x03**^{202} = **0x77**

**0x03**^{203} = **0x99**

**0x03**^{204} = **0xb0**

**0x03**^{205} = **0xcb**

**0x03**^{206} = **0x46**

**0x03**^{207} = **0xca**

**0x03**^{208} = **0x45**

**0x03**^{209} = **0xcf**

**0x03**^{210} = **0x4a**

**0x03**^{211} = **0xde**

**0x03**^{212} = **0x79**

**0x03**^{213} = **0x8b**

**0x03**^{214} = **0x86**

**0x03**^{215} = **0x91**

**0x03**^{216} = **0xa8**

**0x03**^{217} = **0xe3**

**0x03**^{218} = **0x3e**

**0x03**^{219} = **0x42**

**0x03**^{220} = **0xc6**

**0x03**^{221} = **0x51**

**0x03**^{222} = **0xf3**

**0x03**^{223} = **0x0e**

**0x03**^{224} = **0x12**

**0x03**^{225} = **0x36**

**0x03**^{226} = **0x5a**

**0x03**^{227} = **0xee**

**0x03**^{228} = **0x29**

**0x03**^{229} = **0x7b**

**0x03**^{230} = **0x8d**

**0x03**^{231} = **0x8c**

**0x03**^{232} = **0x8f**

**0x03**^{233} = **0x8a**

**0x03**^{234} = **0x85**

**0x03**^{235} = **0x94**

**0x03**^{236} = **0xa7**

**0x03**^{237} = **0xf2**

**0x03**^{238} = **0x0d**

**0x03**^{239} = **0x17**

**0x03**^{240} = **0x39**

**0x03**^{241} = **0x4b**

**0x03**^{242} = **0xdd**

**0x03**^{243} = **0x7c**

**0x03**^{244} = **0x84**

**0x03**^{245} = **0x97**

**0x03**^{246} = **0xa2**

**0x03**^{247} = **0xfd**

**0x03**^{248} = **0x1c**

**0x03**^{249} = **0x24**

**0x03**^{250} = **0x6c**

**0x03**^{251} = **0xb4**

**0x03**^{252} = **0xc7**

**0x03**^{253} = **0x52**

**0x03**^{254} = **0xf6**

… and only now do we circle around to…

**0x03**^{255} = **0x01**

This gives us a one-to-one mapping between the 255 powers 0 through 254 and the 255 non-zero bytes **0x01** through **0xff**.

Here are the perl scripts I used to generate these:

use strict;
my $m = 0x11b;
my $a = 1;
for (my $i = 0; ; $i++) {
printf("0x02^%2d = 0x%02x\n", $i, $a);
if ($i > 0 and $a == 1) { last; }
$a <<= 1; # * 0x02
if ($a > 0xff) { $a ^= $m; }
}
print "\n";
$a = 1;
for (my $i = 0; ; $i++) {
printf("0x03^%3d = 0x%02x\n", $i, $a);
if ($i > 0 and $a == 1) { last; }
$a = ($a << 1) ^ $a; # * 0x03
if ($a > 0xff) { $a ^= $m; }
}

Now that we have this orbit, we use it to construct two lookup tables:

// 255 entries xPlusOne_ToThe[0] = 0x01 to xPlusOne_ToThe[254] = 0xf6
xPlusOne_ToThe[] = { 0x01, 0x03, 0x05, ..., 0x52, 0xf6 };

And the inverse mapping:

// 255 entries logXPlusOne_Of[0x01] = 0 to logXPlusOne_Of[0xff] = 7
logXPlusOne_Of[] = { UNDEFINED, 0, 25, 1, 50, ..., 112, 7 };

Now that we have paid for these 512-ish bytes, we can do multiplication very cheaply:

multiply(b1, b2) {
if (0x00 == b1 || 0x00 == b2) { return 0x00; }
logAns = logXPlusOne_Of[b1] + logXPlusOne_Of[b2];
if (logAns >= 255) { logAns -= 255; }
return xPlusOne_ToThe[logAns];
}