המצב מאותחל להיות הקלט (כלומר הבלוק), והוא ניתן להצגה כמערך בתים דו ממדי בעל ארבע שורות ומספר עמודות המסומן formula_1 שהוא פונקציה של גודל הבלוק בסיביות חלקי 32 (ראה טבלה). באופן דומה מיוצגים בתי המפתח מארבע שורות ומספר עמודות המסומן formula_2 - פונקציה של אורך המפתח בסיביות חלקי 32.
בחלק מהפעולות בצופן מתייחסים לבלוקים של צופן ומפתח כאל מערך של וקטורים בני ארבעה בתים כאשר כל ווקטור מייצג עמודה אחת. קלט ופלט הצופן מיוצגים כמערך חד-ממדי של בתים בגודל formula_3 (דהיינו 16, 24 או 32 בתים בהתאם לגודל הבלוק - בתקן נקבע ל-16 בתים) שממופים למערך הדו ממדי לפי סדר עמודות ראשי כמתואר בתרשים משמאל. 16 הבתים של הקלט formula_4 מסודרים במטריצה formula_5 מלמעלה למטה החל מהעמודה השמאלית והאינדקס לכל כניסה הוא formula_6 למשל formula_7 מתייחס לערך המצוי בשורה הראשונה בעמודה השנייה. באופן דומה מיוצג המפתח. הערך formula_8 מציין את מספר הסבבים הדרוש והוא תלוי בערכים formula_9 (ראה טבלה).
תיאור בלוק מצב הביניים State כמערך דו ממדי בגודל 4x4 בתים
בכל סבב מבצעים ארבע פונקציות שונות על טבלת המצב, המוצגות בפסאודו קוד הבא:
formula_10
כאשר בסבב האחרון משמיטים את הפעולה MixColumn.
פירוט הטרנספורמציות מתואר להלן.
הטרנספורמציה SubBytes.
הטרנספורמציה SubBytes היא פונקציית החלפת בתים פשוטה ואי-ליניארית הפועלת באופן סדרתי על כל בתי המצב (ללא תלות אחד בשני) על פי טבלת החלפה קבועה והפיכה הנקראת S-Box.
בקוד:
void subBytes(byte *state)
for (int i = 0; i &lt; 16; i++)
state[i] = sbox[state[i]];
במקרה של פענוח (decrypt), משתמשים בתיבות ההחלפה ההפוכית - rSBox:
void iSubBytes(byte *state)
for (int i = 0; i &lt; 16; i++)
state[i] = rsbox[state[i]];
תיבות ההחלפה של ריינדל.
בניגוד ל-DES, ערכי התיבות (S-box) של AES אינם ערכים אקראיים כלשהם שעונים על המאפיינים הדרושים, אלא הם בעלי מבנה אלגברי מובהק. למעשה אפשר לחשב אותם על ידי שילוב של שתי טרנספורמציות. הראשונה היא חישוב הופכי כפלי של הקלט בשדה formula_15 מודולו formula_16 כאשר אפס ממופה לעצמו כך שאם הקלט לפונקציה הראשונה של formula_17 הוא formula_18 הפלט הוא formula_19. הפעולה השנייה היא טרנספורמציה אפינית של הפלט מעל השדה formula_20 שתפקידה "לטשטש" את המבנה האלגברי הייחודי שלו. הטרנספורמציה האפינית היא הכפלה במטריצה בינארית הפיכה קבועה מסדר formula_21 סיביות וחיבור עם וקטור קבוע באורך 8 סיביות. לצורך המחשה אם הקלט לתיבת ההחלפה הוא formula_22 ההופכי הכפלי הוא formula_23 כך שמתקיים formula_24, אחרי הטרנספורמציה האפינית מתקבל formula_25. הטרנספורמציה האפינית מומחשת בתרשים הבא:
formula_26
לפענוח מבצעים בסדר הפוך, דהיינו תחילה טרנספורמציה אפינית הפוכה ואז חישוב הופכי כפלי.
ביישום האלגוריתם בפועל, במערכת בה קיים זיכרון זמין רב, מעדיפים שימוש בטבלת איחזור (lookup table) כדי להימנע מפעולות כפל במטריצות בזמן ריצה. בטבלת האיחזור של ריינדל מקודדים את כל 256 הערכים האפשריים מראש ובזמן ריצה פשוט מחליפים את הקלט בערך המתאים לפי אינדקס. למשל, אם הבית הראשון של המצב הוא formula_27, התוצאה תהיה הערך שנמצא בשורה 12 בעמודה השלישית (formula_28). אם טבלאות ההחלפה של ההצפנה והפענוח מוכנות מראש הקוד לביצוע ההחלפה מאוד פשוט, כל 16 בתי המצב מוחלפים בזה אחר זה בערכי תיבות ההחלפה המתאימות:
הטרנספורמציה ShiftRow.
הזזת שורות המצב בהזזה מעגלית לפי ערכים שונים. השורה הראשונה נותרת במקומה והשורות הבאות מוזזות לפי היסטים קבועים formula_29 בהתאמה. ההיסטים תלויים בגודל הבלוק formula_1 (בתקן הערכים הם 1,2,3 בהתאמה). הקוד הבא מתאר את הטרנספורמציה לפי התקן:
void shiftRows(byte *state)
for (int i = 0; i &lt; 4; i++)
for (int j = 0; j &lt; i; j++)
byte tmp = state[0];
for (int k = 0; k &lt; 3; k++)
state[k] = state[k+1];
state[3] = tmp;
state += 4; // read the next state
הפעולה ההפוכה של טרנספורמציה זו מושגת על ידי הזזה לפי formula_31 בהתאמה. כך שהבית בפוזיציה formula_32 בשורה formula_33 מוסט לפוזיציה formula_34. כדלהלן:
void iShiftRows(byte *state)
for (int i = 0; i &lt; 4; i++)
for(int j = 0; j &lt; i; j++)
byte tmp = state[3];
for (int k = 3; k &gt; 0; k--)
state[k] = state[k-1];
state[0] = tmp;
state += 4; // get next state
הטרנספורמציה MixColumn.
עמודות המצב מיוצגות כפולינומים מעל formula_15 ומוכפלים (מודולו formula_36) בפולינום קבוע formula_37 שהוא זר ל-formula_36 ולכן יש לו הופכי. המקדמים מוצגים בבסיס הקסדצימלי. אפשר לתאר את הפעולה הזו ככפל במטריצה formula_39 כך:
formula_40
להלן הקוד שמשתמש בפונקציית העזר Gmul (מוגדרת למטה):
void mixColumns(byte *state)
byte column[4];
for (int i = 0; i &lt; 4; i++)
for (int j = 0; j &lt; 4; j++)
column[j] = state[(j*4)+i];
byte t[4]; // copy the column bytes to a temporary array
t[0] = column[0], t[1] = column[1], t[2] = column[2], t[3] = column[3];
column[0] = Gmul(t[0],2) ^ Gmul(t[3],1) ^ Gmul(t[2],1) ^ Gmul(t[1],3);
column[1] = Gmul(t[1],2) ^ Gmul(t[0],1) ^ Gmul(t[3],1) ^ Gmul(t[2],3);
column[2] = Gmul(t[2],2) ^ Gmul(t[1],1) ^ Gmul(t[0],1) ^ Gmul(t[3],3);
column[3] = Gmul(t[3],2) ^ Gmul(t[2],1) ^ Gmul(t[1],1) ^ Gmul(t[0],3);
for (int j = 0; j &lt; 4; j++)
state[(j*4)+i] = column[j];
ולפענוח מבצעים פעולה דומה. כל עמודה מוכפלת בפולינום formula_41 שהוא הופכי כפלי של formula_42. הקוד נראה כך:
void iMixColumns(byte *state)
byte column[4];
for (int i = 0; i &lt; 4; i++)
for (int j = 0; j &lt; 4; j++)
column[j] = state[(j*4)+i];
byte t[4]; // copy the column bytes to temporary array
t[0] = column[0], t[1] = column[1], t[2] = column[2], t[3] = column[3];
column[0] = Gmul(t[0],0x0E) ^ Gmul(t[3],0x09) ^ Gmul(t[2],0x0D) ^ Gmul(t[1],0x0B);
column[1] = Gmul(t[1],0x0E) ^ Gmul(t[0],0x09) ^ Gmul(t[3],0x0D) ^ Gmul(t[2],0x0B);
column[2] = Gmul(t[2],0x0E) ^ Gmul(t[1],0x09) ^ Gmul(t[0],0x0D) ^ Gmul(t[3],0x0B);
column[3] = Gmul(t[3],0x0E) ^ Gmul(t[2],0x09) ^ Gmul(t[1],0x0D) ^ Gmul(t[0],0x0B);
for (int j = 0; j &lt; 4; j++)
state[(j*4)+i] = column[j];
פונקציית הוספת מפתח (AddRoundKey).
מעדכנים את ערכי כל בתי המצב באמצעות חישוב XOR עם מפתח הסבב (round key).
את מפתח הסבב מפיקים ממפתח הצופן באמצעות התהליך המתואר להלן (Key Expansion) וגודלו נקבע לפי גודל הבלוק formula_1. לדוגמה המערך הדו ממדי של המצב הוא formula_44 כאשר מספר העמודה formula_45 ומספר הסבב formula_46, מחשבים את formula_47 (הסוגריים המרובעים מייצגים את המילה הנוצרת מעמודה formula_48 של המצב). הפעולה ההפוכה בתהליך הפענוח זהה כיוון שהפעולה XOR הופכית של עצמה. להלן הקוד של שכבת הוספת המפתח:
void addRoundKey(byte *state, byte *roundKey)
for (int i = 0; i &lt; 16; i++)
state[i] ^= roundKey[i];
תהליך הרחבת מפתח (Key Expansion).
מפתחות הסבבים הם פונקציה של מפתח הצופן המסופק על ידי המשתמש. סך כל סיביות המפתח המורחב שווה לגודל הבלוק הנבחר כפול מספר הסבבים ועוד אחד. למשל עבור בלוק של 128 סיביות יהיו 1408 סיביות מפתח מורחב, אותם מחלקים ל-formula_1 מילים לפי הסדר. למעשה מתקבל מערך חד ממדי של 44 מילים בגודל 4 בתים המסומן formula_50 כאשר בכל סבב משתמשים ב-formula_1 המילים הבאות.
תחילה מחלקים את מפתח הצופן למילים בגודל 32 סיביות אותם מציבים ב-formula_2 הכניסות הראשונות במערך. יתר הכניסות מחושבות באופן רקורסיבי על בסיס ערכים קודמים. כל מילה formula_53 היא חישוב XOR עם מילה formula_54 ועבור מילים שמיקומם הוא כפולה של formula_2 מופעלת טרנספורמציה נוספת שכוללת הזזה מעגלית של בתי המילה בנוסף להחלפה בתיבות ההחלפה וחיבור XOR עם הקבועים formula_56 (ראה להלן). פרוצדורת הרחבת המפתח תלויה ב-formula_2 כאשר formula_58 הרחבת המפתח מתוארת בפסאודו קוד הבא:
formula_59
כאן הפונקציה formula_64 מחזירה ארבעה בתים שהם תוצאה של הפעלת תיבות ההחלפה של ריינדל על בתי הקלט. הפונקציה formula_65 מחזירה ארבעה בתים שהם תמורה מחזורית של עצמם. דהיינו שינוי סדר הבתים, אם בתי הקלט הם formula_66 התוצאה תהיה formula_67.
הקבועים formula_56 המשמשים להרחבת המפתח הם סדרה של עשרה ערכים בגודל 32 סיביות מהצורה formula_69. ערכי formula_70 מייצגים חזקות הפולינום formula_71 דהיינו מתחילים ב-formula_72 ואז מחשבים formula_73. עבור מפתח 128 סיביות הקבועים הם:
במפתח 256 סיביות משתמשים רק בשבעת הערכים הראשונים. וכן אם המפתח הנבחר בגודל 256 שאז formula_75, נוספת פעולה לאחר שורה 2 כדלהלן:
סיכום הפרוצדורה להרחבת המפתח בקוד C++ הוא:
void createRoundKey(byte *expKey, byte *roundKey)
for(int i = 0; i &lt; 4; i++)
for(int j = 0; j &lt; 4; j++)
roundKey[(i+(j*4))] = expKey[(i*4)+j];
void expandKey(byte *expKey, byte *key, int size, size_t expKeySize)
int currSize = 0, rconIter = 1;
byte t[4] = {0};
for (int i = 0; i &lt; size; i++)
expKey[i] = key[i];
currSize += size;
while ((unsigned)currSize &lt; expKeySize)
for(int i = 0; i &lt; 4; i++)
t[i] = expKey[(currSize - 4) + i];