やっぱり通信の基本は暗号化であるため、その一つとして共通鍵暗号に使われているアルゴリズムを学んでいこうと思います。

私はc言語が好きなので、いやそれしかまともに使えないので、いやまともに使えているか怪しいけれど、c言語で挑戦してみます。


そもそもAESはc言語かjavaで実装されているみたいで、それを自分で作るとか車輪の再発明そのものなので無駄です。

よかったー暇で、どうせやることない無駄な時間なのでちょうどいいです。


AESとは

AESはブロック暗号で一定ビットずつ暗号化する。

暗号化は

1.文字を置き換える(SubBytes)

2.4バイトずつ左シフト(ShiftRows)

3.4バイトずつ行列変換(MixColumns)

4.XORとか(AddRoundKey)

の4種類を何回か繰り返すらしい。


やってみる

1.置換(SubBytes)

 置換の対応表(S-BOX)のコードが

C#でAES暗号化アルゴリズムを外部ライブラリに一切頼らず完全実装してみた

にあったのでコピペで。

1バイトずつ変換する。

0なら0x63に、0xa0なら0xe0になる。


2.左シフト(ShiftRows)

16バイトを4バイトずつ分けて、
1つ目は0バイトシフト(何もしない)、
2つ目は1バイトシフト、3は2、4は3と、
n個目をn-1バイトシフトする。

さっきのサイトを参考。

3.行列変換(MixColumns)

よし、次は行列変換だ!と思って調べていたら
既にc言語でAESを実装しているサイトがありました。
まじかよ、コピーサイトになってしまう。
と思ったけれどもっとかみ砕いて自分用に説明したものを書けばオリジナリティが出ると思うので続けます。

私は行列が苦手なのでこの部分がさっぱり理解できません。
ただの掛け算と足し算と思ったら全然違いました。
これを読んでもさっぱりです。
x8+x4+x3+x+1を法とした掛け算の部分が理解できません。
0b100011010で割ればいいのかと思ったら違うみたいだしどゆこと。
AESの説明を調べても大体この部分だけ「数学的な知識を必要とするので省略します」とかしか書いて無くて数学科じゃない私には厳しすぎる。



このサイトにわかりやすく説明があったおかげでなんとなく理解できました!
GF(2^8)がどんな意味とか多項式を法にするという意味がなんとなく分かったくらいですが。
ガロア体だからただの行列の掛け算じゃなくてxorとか出てくるんですね。

AESはこの行列変換を暗号化の一部として含んでいますが、これだけを用いた暗号化のことをヒル暗号というらしいです。


よーし、なんとなく分かったから実装しよう!と思ったけれどやっぱり理解できていなかった。
そのため「AES (Rijndael) の MixColumns を理解する」のコードを自分で読んで理解しようと思いました。
なぜかわかりやすく書いてあるはずなのにその説明すら理解できなかったので......

xtime()を読んでみます。

unsigned char xtime(unsigned char b)
{
    return (b << 1) ^ ((b & 0x80) ? 0x1b : 0x00);
}

bを受け取り、bを左に1シフトした値と、bと0b10000000をandして1だったら0x1bを、0なら0とxorすると。
すなわち受け取った値のx^7に対応するビットが1なら0x1bとxorすると。
0x1bはx^8+x^4+x^3+x+1をビット表記にして1バイトで切り捨てた値であり、それとxorすることでその法既約多項式で引いた(割った)ことになると。
なんでb<<1したんだろう。
b<<1は2倍した、x倍したという意味だが、なんでするんだろう。
数学が苦手すぎて困る。

この説明の下にある例を見てみる。
0x57に各ビットを掛けた結果が並べてある。
これはどういう意味だろう。
0x57*0x1=0x57
0x57*0x2=0xae
ここまでわかる。
0x57*0x4=0x47 !?
0x57に0x4をかけると0x15cとなる。
これは
x^8+x^4+x^3+x+1=0b100011011=0x11bを超えているから0x11bで割った余りにする必要がある。

それで0x11bの2倍の0x236より大きくないから1倍の0x11bを引いて
0x15c-0x11b=0x41
ではない。
ガロア体だから普通の計算とは違ってxorで足し引きすると。
よって
0x15c xor 0x11b=0x47
おお、一致した。

GF(5)なら
3*3=9 mod 5=9-5*1=4 
4*4=16 mod 5=16-5*3=1
のように普通に計算結果から割って余りを出せばいいだけなのに。

次に0x57*0x8=0x8e !?
まあまあまあ、さっきと同じようにxor取ればいいってことよ。
0x57*0x8→0x2b8 xor 0x11b→0x3a3 !?
あれ!?
0x2b8は0x236より大きいから、法既約多項式が1個分じゃなくて2個分なのか。
じゃあ0x11bを2回引けばいいのか。
でも2回xorしても元に戻って意味ないな。
じゃあガロア体だと一気に割ることができないのか。
やっぱり多項式同士の掛け算だから、その通り計算しないといけないのか。
0x57*0x8
=0b01010111*0b1000
=(x^6+x^4+x^2+x+1)*(x^3)
=x^9+x^7+x^5+x^4+x^3
x^8+x^4+x^3+x+1で割るから
=(x^8+x^4+x^3+x+1)(b_7x^7+b_6x^6+b_5x^5~~)
こんな形に変形して割って
=b_7x^7+b_6x^6+b_5x^5~~
こんなかんじになるわけか。

実際に筆算のように解くと
まずx^9があるから、x^8+x^4+x^3+x+1にxを掛けたものを引くと
(x^9+x^7+x^5+x^4+x^3)-(x^9+x^5+x^4+x^2+x)
=x^7+x^3-x^2-x
引き算はxorであるため、その項を追加したことになるから
=x^7+x^3+x^2+x

よって
0x57*0x8
=x^7+x^3+x^2+x
=0b10001110
=0x8e

やっとできた。
なるほど!!
このガロア体の多項式では係数は必ず0か1しかとりえないため、多項式で割るときはxで割るしかないのか。
それでx^7より大きい項がなくなるまで既約多項式とx(2)を掛けてたものを引いて(xorして)いけばいいと。

この方法でもう一度解いてみると、
0x57*0x8
=0x2b8
=0b1010111000
0b1000000000=x^9があるから
0x11b=0b100011011=x^8+x^4+x^3+x+1 にx (2)をかけたものを引く(xor)して
0x2b8 xor (0x11b*0x2)
=0x8e
=0b10001110
これでx^7より大きいビットがなくなったため計算終了と。

よし!もう一問だけ。
0x57*0x10
=0x570
=0b10101110000
よって
x^10があるから、x^2を既約多項式に掛けて
=0x570 xor (0x11b*0x4)
=0x11c
=0b100011100
まだx^8があるから
=0x11c xor 0x11b
=0x7
=0b111
これでx^7以下に収まって終了と。

よし、完璧に理解した。
それでコードを読んでみると、、
なんか違う。
しかもunsigned char の1バイト内だとこの方法じゃ計算できない。

よくみると
0x57*0x4=xtime(0x57)
と書いてある。
xtime()では与えられた値にx^7の項があれば、その値にxを掛けたものと0x1bをxorしている。
実際にやってみると
0x57
=0b1010111
よりx^6までだから0x1bとxorせずにxを掛けるだけで
0x57*0x2
=0xae
マジで0x57*0x2の答えになっていやがる。

じゃあ次は0x57*0x4。
xtime(0xae)
0xae
=0b10101110
x^7があるからx掛けた値と0x1bをxorして
(0xae*0x2) xor 0x1b
=0x147
ただし1バイト切り捨てのため
=0x47
できている、だと......
どういうことだ。

多項式として0x57*0x4を考えると
0x57*0x4
=0x57*(0x2*0x2)
=(0x57*0x2)+(0x57*0x2)
=(0xae)+(0xae)
=0xae*0x2
=0xae<<1
=xtime(0xae)

ほんとだ、そゆことか。
てかサイトにもちゃんと書いてあった。
自分でやらないとちゃんと頭に入ってこない系だからしっくり来ていなかった。
各ビットは2倍の差があるのだから、次の位のビットで計算したら答えも2倍になる。
このとき一つ下の位のビットの計算は既に既約多項式で割られている値だから、2倍した値は多くて1回しか既約多項式で割る必要がない。
なぜなら今回の既約多項式で割られた値は必ずx^7以下になり、最大で0b11111111=0xffであるから0xffを2倍しても0b111111110となってx^8までしか大きくならないからである。
そして既約多項式で割る判断は2倍する前の値がx^7未満であること。
なぜならx^6までなら2倍してもx^8の項は出てこないからである。

だからさっきのサイトのすぐ下にあるdot()では

unsigned char dot(unsigned char x, unsigned char y)
{
    unsigned char mask;
    unsigned char product = 0;

    for (mask = 0x01; mask; mask <<= 1) {
        if (y & mask) {
            product ^= x;
        }
        x = xtime(x);
    }
    return product;
}

x*yにおいて、まずyのビットを一番下位から1のものを探す。
1だったらproductにxをxorする(足す)。
その後、xtime()でxにx(2)を掛けて既約多項式を超えるなら割るという作業が行われていると。
下位のビットから足して既約多項式ですぐ割ることで1バイト内で計算をすますことができているのか。
すごく時間がかかったがたぶんやっと本当に理解できたと思う。



それでこの掛け算とxorの足し算を利用してMDS行列と掛け合わせると。
MDS行列の説明にf(k)=Akとか書かれているので、たぶん空間的に元の座標の整数倍の座標にバラバラに置かれ、逆元をかけるとその座標から戻ってくるイメージなのかなとか勝手に思っています。

4.XORとか(AddRoundKey)

4バイトずつ
RotWord:ずらす
SubWord:Sboxで置換
Rcon:定数とxor
するらしいです。

なんかこの部分も言葉による説明が少ないような。
ここも理解に苦しみます。
これを読んでみても英語で分からぬ。
AddRoundKeyとkeyexpansionは違う関数みたいです。
AddRoundKeyはwという変数とxorすると。
key_expansionは

extern void key_expansion(const uint32_t* key /*Nk*/, uint32_t* w /*Nb*(Nr+1)*/)
{
  int i;
  uint8_t Nr = key_round_table[aes_type].Nr;
  uint8_t Nk = key_round_table[aes_type].Nk;

  memcpy(w, key, Nk*4);
  for (i=Nk; i<Nb*(Nr+1); i++) {
    uint32_t temp = w[i-1];
    if (i%Nk == 0) {
      temp = sub_word(rot_word(temp)) ^ rcon[i/Nk];
    } else if (6<Nk && i%Nk == 4) {
      temp = sub_word(temp);
    }
    w[i] = w[i-Nk] ^ temp;
  }
}

wに鍵をコピーした後、128bitでは鍵のサイズを表すNk=4、列数Nb=4,ラウンド数Nr=10よりfor文でi=4からi=4*(10+1)=44未満までi++で繰り返される。
temp=w[i-1];
iが4で割り切れたとき、temp=sub(rot(temp))^rcon[i/4];
elseifはNk=4では起こらない。
その後w[i]=w[i-4]^temp

iが4で割り切れたときってなんだ?
まずwの存在を理解していないのか。
さっきの英語のサイトを読むと、wは4*4のブロックの縦の列をwで表しているみたい。
それでいてkey scheduleも表しているっぽい。
なにそれ?
Every following word, w[i], is equal to the XOR of the previous word, w[i-1], and
the word Nk positions earlier, w[i-Nk].
どうしてもこれが理解できない。
DeepL翻訳すると
続くすべての単語w[i]は、前の単語w[i-1]とNk個前の単語w[i-Nk]のXORに等しくなります。
あ、どのw[i]に対しても、w[i-1] xor w[i-4]がw[i]になるようなwをkey_expansion()で用意するってことか。
なんでこんなwを用意するかは考えなくていいか。
こうするときっとなんかいい感じのラウンドキーになるんだろう()
とにかく10ラウンド暗号化を繰り返すとき、AddRoundKey()では暗号化するもの(inputと呼ぶ)とwをxorする。
そのwは最初にkey_expansion()で44個用意されると。
1回にinputの最初の4バイトとxorし、毎回異なるラウンドキーとxorするため、10ラウンドあるから4*10=40個必要。
また最初にxorするからその4バイト。
合わせて44バイト。

戻るけどなんでi%4==0のときだけrotとかsubするんだろう?
いや、これもそういうものなのか。
いやいやいや、ちがうわ。
その前にこの引数の型がunsigned charじゃなくてuint32_tだ。
unsigned intだから4バイトだ。
で、i%4==0のときはそのラウンドキーの最初のw(列)を扱うときだけでいいからか。
これは後で説明します。

ちなみに私がuint8_tとかbyteを使わないでunsigned charを使うのはラッピングされていなくてなんか一番基本の型って感じがするからです。

繰り返し

AESはブロック暗号であり、一定ビットずつ暗号化します。
そのビットの種類には128bit,192bit,256bitの3種類あるらしいです。
その種類によって暗号化する手順が少し異なるらしいので、今回は一番単純そうな128bitでやっています。
(128bitの解説が多いし)
今までに説明した4種類の暗号化を何回か繰り返すわけですが、128bitでは10回、192bitでは12回、256bitでは14回行います。
そして128bitでは
1回目は鍵とのXORだけ。
2~9回目は4種類。
10回目はMixColumnsがない3種類。
こんな感じで繰り返すと。

復習

遂に、実装できました!
暗号化だけですけど。
この英語のサイト、超必要なものでした!
デバッグにめちゃくちゃ便利でした!
なぜなら最後の方にAppendix(付録)として各値が途中で取り得る値の表を載せてくれていたからです。
これのおかげでどこが間違っているかがわかります。
C#で書いた人もこのサイトで間違いがないか確認していますね。

それで、上に書いた説明は説明というよりは私がAESを理解するまでの考えをそのまま残しただけです。
そのためこれからわかりやすくまとめようと思いました。
いや思っていました。
しかしもう達成感で面倒くさくなったので、重要なところだけまとめます。

全体

全体の流れは

まずAESはブロック暗号でAES128bitなら128/8=16バイトずつ暗号化をしていく。

そのため暗号化したい16バイト(これをinputと呼ぶ)を用意する。

そしてAESは共通鍵暗号のため、16バイトの鍵を用意する。

あとは下の順のように回していくと。

ラウンド0

(key_expansion)

AddRoundKey

ラウンド1-9

SubBytes

ShiftRows

MixColumns

AddRoundKey

ラウンド10

SubBytes

ShiftRows

AddRoundKey

暗号化完了!

SubBytesはただの置換、ShiftRowsはただの入れ替えのためそんなに難しくはない。
問題はMixColumns、AddRoundKeyの2つである。

MixColumns

またinputを暗号化している途中の16バイトをstateと呼ぶことにする。
MixColumnsはMDS行列とstateをガロア拡大体GF(2^8)の中で計算する。
そのためガロア拡大体の計算方法を理解しないといけない。
今回出てきたガロア拡大体は、数値の各ビットを2の累乗ではなくxの累乗として扱うことで多項式としてとらえる計算であった。
実際に多項式として具体的な数値の計算をして仕組みが分かればプログラムに落とし込めるはず。
私はサイト見まくったし、またガロア体の概念的なのはあんまよくわからなかったけれど。

AddRoundKey

これ!
これ理解するの疲れたわー。
言葉で説明しているものが少なくてコードを読んで理解しないといけなかった。

まず最初のラウンド0は鍵とinputをxorするだけ。
これはまだいい。
ラウンド1から何すればいいか分からなかった。
そもそもAddRoundKeyを理解していなかった。
AddRoundKeyはstateとラウンド鍵をxorするだけ。
ラウンド鍵は各ラウンドでstateとxorする鍵のことで毎回異なっている。
このラウンド鍵を作るのがkey_expansion。
だから難しいのはkey_expansionの中身。

key_expansion

まずwというラウンド鍵を入れるメモリを用意しておく。
ラウンド鍵は4*4の行列で、このwは列を表している。
4*4の行列が最初のラウンド0も含めて11個ある。
それでそれぞれのラウンド鍵に名前とかついていたか覚えていないから、それぞれRi (R0~R10)とする。

最初にR0に鍵をコピーする。
R1を作っていくので、まずはR1の1列目を作る。
R0の4列目を
RotWord (ただの入れ替え)
SubBytes (さっきと同じ置換)
RCON(定数)とxor (ただのxor)
した後、
R1の1行目にR0の1列目と4列目をxorした値を入れる。

次はR1の2列目を作っていく。
これはR0の2列目とR1の1列目をxorした値を入れる。

こんなかんじに
最初だけwi-1をrot,sub,rconの処理を行って、あとは
wi=wi-4 xor wi-1
って感じに入れていく。


こんな風にR1~R10を用意すると。
これがわかるまでほんとに時間がかかった。。

まとめ

あとは復号とかパディングとかの処理もやりたかったけどもうめんどいので気が向いたらということで。
てか
ここにc言語で128bit以外も対応しているコードがあるし意味ないしなー。
でも自分で書いたから少しは暗号化について学べたと思う!

コード

自分の冗長なコードをあえてgithubではなくここに書くっていう。
デバッグの時のままだから、デバッグ用のコードを消せばもう少しきれいに見えるはず。
stdio.hしか使っていないところが、なんか自分で書いた感じがあったいい。

#include<stdio.h>

unsigned char input[] =
{
	0x32, 0x88, 0x31, 0xe0,
	0x43, 0x5a, 0x31, 0x37,
	0xf6, 0x30, 0x98, 0x07,
	0xa8, 0x8d, 0xa2, 0x34
};

unsigned char key[] = 
{
	0x2b, 0x28, 0xab, 0x09,
	0x7e, 0xae, 0xf7, 0xcf,
	0x15, 0xd2, 0x15, 0x4f,
	0x16, 0xa6, 0x88, 0x3C
};

unsigned char w[16*11];

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

unsigned char mixc[] =
{ 
        0x02, 0x03, 0x01, 0x01,
        0x01, 0x02, 0x03, 0x01,
        0x01, 0x01, 0x02, 0x03,
        0x03, 0x01, 0x01, 0x02
};

unsigned char RCON[] =
{
    0,0x1,0x2,0x4,0x8,0x10,0x20,0x40,0x80,0x1B,0x36
};

void SubBytes(unsigned char *a)
{
	for(int i=0;i<16;i++){
		a[i]=sbox[a[i]];
	}
}

void ShiftRows(unsigned char *a)
{
	unsigned char temp;
	temp=a[4];a[4]=a[5];a[5]=a[6];a[6]=a[7];a[7]=temp;
	temp=a[8];a[8]=a[10];a[10]=temp;temp=a[9];a[9]=a[11];a[11]=temp;
	temp=a[12];a[12]=a[15];a[15]=a[14];a[14]=a[13];a[13]=temp;
}

unsigned char gmul(unsigned char x,unsigned char y)
{
	unsigned char product=0;
	for(unsigned char i=1;i;i<<=1){
		if(y&i)product^=x;
		if(x&0b10000000)x=(x<<1)^0x1b;
		else x<<=1;
	}
	return product;
}

void MixColumns(unsigned char *a)
{
	unsigned char temp[4];
	for(int i=0;i<4;i++){
		temp[0]=gmul(mixc[0],a[0+i])^gmul(mixc[1],a[4+i])^gmul(mixc[2],a[8+i])^gmul(mixc[3],a[12+i]);
		temp[1]=gmul(mixc[4],a[0+i])^gmul(mixc[5],a[4+i])^gmul(mixc[6],a[8+i])^gmul(mixc[7],a[12+i]);
		temp[2]=gmul(mixc[8],a[0+i])^gmul(mixc[9],a[4+i])^gmul(mixc[10],a[8+i])^gmul(mixc[11],a[12+i]);
		temp[3]=gmul(mixc[12],a[0+i])^gmul(mixc[13],a[4+i])^gmul(mixc[14],a[8+i])^gmul(mixc[15],a[12+i]);
		
		a[0+i]=temp[0];
		a[4+i]=temp[1];
		a[8+i]=temp[2];
		a[12+i]=temp[3];
	}
}

void RotWord_4(unsigned char *a)
{
	//4列目だけ
	unsigned char temp;
	temp=a[0+3];
	a[0+3]=a[4+3];
	a[4+3]=a[8+3];
	a[8+3]=a[12+3];
	a[12+3]=temp;
}

void xorRCON_4(unsigned char *a,int n)
{
	//4列目だけ
	//かつw全体にxorだから一番下位のバイト分だけ
	a[0+3]^=RCON[n];
}

void xor1and4(unsigned char *a,unsigned char *b)
{
	//最初にtempの分を計算しちゃう
	a[0]=b[0]^b[0+3];
	a[4]=b[4]^b[4+3];
	a[8]=b[8]^b[8+3];
	a[12]=b[12]^b[12+3];
	for(int i=1;i<4;i++){
		a[0+i]=a[0+i-16]^a[0+i-1];
		a[4+i]=a[4+i-16]^a[4+i-1];
		a[8+i]=a[8+i-16]^a[8+i-1];
		a[12+i]=a[12+i-16]^a[12+i-1];
	}
}

void SubBytes_4(unsigned char *a)
{
	//4列目だけ
	a[0+3]=sbox[a[0+3]];
	a[4+3]=sbox[a[4+3]];
	a[8+3]=sbox[a[8+3]];
	a[12+3]=sbox[a[12+3]];
}

void print128(unsigned char *a)
{
	for(int i=0;i<16;i++){
		if(i%4==0&&i>0)puts("");
		printf("%02X ",a[i]);
	}
	puts("\n");
}

void key_expansion(unsigned char *key, unsigned char *w)
{
	for(int i=0;i<16;i++)w[i]=key[i];
	
	 for (int i=1;i<11;i++) {
	 	unsigned char temp[16];
	 	for(int j=0;j<16;j++)temp[j]=w[0+(i-1)*16+j];
	 	RotWord_4(temp);
	 	//puts("rot");
	 	//print128(temp);
		SubBytes_4(temp);
	 	//puts("sub");
	 	//print128(temp);
		xorRCON_4(temp,i);
	 	//puts("rcon");
	 	//print128(temp);
		xor1and4(&w[i*16],temp);
	}
}

void AddRoundKey(unsigned char *a,int n)
{
	if(n==0){
		key_expansion(key,w);
	}
	for(int i=0;i<16;i++){
		a[i]^=w[i+n*16];
	}
}

int main()
{
	puts("input");
	print128(input);
	puts("key");
	print128(key);
	puts("add_first");
	AddRoundKey(input,0);
	print128(input);
		
	for(int i=1;i<=9;i++){
		printf("ROUND%d\n",i);
		printf("round%d_key\n",i);
		print128(w+16*i);
		puts("sub");
		SubBytes(input);
		print128(input);
		puts("Shift");
		ShiftRows(input);
		print128(input);
		puts("mix");
		MixColumns(input);
		print128(input);
		puts("add");
		AddRoundKey(input,i);
		print128(input);
	}
	printf("ROUND10\n");
	printf("round%d_key\n",10);
	print128(w+16*10);
	puts("sub");
	SubBytes(input);
	print128(input);
	puts("Shift");
	ShiftRows(input);
	print128(input);
	puts("add");
	AddRoundKey(input,10);
	print128(input);
	return 0;
}




参考