if(currSize % size == 0)
byte tmp = t[0];
for(int i = 0; i &lt; 3; i++)
t[i] = t[i+1];
t[3] = tmp;
for(int i = 0; i &lt; 4; i++)
t[i] = sbox[t[i]];
t[0] = t[0] ^ rcon[rconIter++];
if (size == 32 &amp;&amp; ((currSize % size) == 16))
for(int i = 0; i &lt; 4; i++)
t[i] = sbox[t[i]];
for(int i = 0; i &lt; 4; i++)
expKey[currSize] = expKey[currSize - size] ^ t[i];
currSize++;
היבטים מתמטיים.
רוב הפעולות בצופן הן פעולות אלגבריות על בתים המייצגים איברים של השדה formula_15. הסיבות לבחירה זו הן שכל איבר בשדה ניתן לייצוג על ידי בית אחד במחשב וכן בגלל תכונת האיזומורפיות. מסיבות של יעילות נבחר ייצוג פולינומי רגיל, דהיינו הבית formula_79 מתאר פולינום ממעלה 7 עם מקדמים formula_80 מהייצוג הבינארי שלו, כאשר formula_81 מייצג את המקדם הראשון או החופשי ו-formula_82 המקדם המוביל. בניסוח רשמי:
אף על פי שבזיכרון האלמנטים מאוחסנים כמספרים שלמים, יש הבדל מהותי בין אריתמטיקה במספרים שלמים לבין חשבון פולינומים מעל הרחבה של השדה הבינארי. לשם הנוחות המקדמים מוצגים כאן בבסיס הקסדצימלי כנהוג בשפת C לדוגמה 0X2D שהוא formula_84 (בייצוג בינארי) מקביל לפולינום formula_85. בכתיב זה משמיטים את המקדמים כי הם מובנים מההקשר. בצורה מפורשת יותר אפשר להציג זאת: formula_86.
בכתיב מקוצר אפשר להציג אלמנט כוקטור formula_87 שמכיל את המקדמים (בזיכרון מאחסנים רק את המקדמים). פעולת החיבור בין אלמנטים בשדה היא חיבור מודולו 2 של המקדמים (פעולה המקבילה ל-XOR המסומן formula_88) לדוגמה formula_89 ובייצוג הקסדצימלי formula_90. באופן פורמלי:
כאשר כל formula_92 הוא מודולו 2. עם פעולה זו מתקבלת חבורה אבלית אסוציאטיבית וקומוטטיבית עם איבר יחידה אפס ואיבר נגדי (כל אלמנט נגדי של עצמו לכן חיסור זהה לחיבור) ואיבר הופכי.
פעולת כפל בין אלמנטים בשדה היא כפל ארוך בפולינומים כשהתוצאה היא:
כאשר כל formula_94 הוא מודולו 2. כעת לפי חוקי החשבון המודולרי היות שהתוצאה אינה בשדה formula_15 (כי המעלה שלה גבוהה משבע) יש צורך לצמצם אותה מודולו פולינום אי-פריק המייצג את השדה (דהיינו שחזקות שלו הם האלמנטים של השדה). ב-AES נבחר הפולינום formula_96 (או formula_97 בבסיס הקסדצימלי) שהוא פולינום אי-פריק ממעלה 8. היות שהפעולות הן רק בגבולות בית אחד, אפשר להתעלם מהסיבית העליונה לכן הצמצום נעשה במחשב עם formula_98. פעולת הכפל המודולרי בניסוח פורמלי היא:
כפל פולינומים מודולרי.
byte px = 0x1b;
byte Gmul(byte a, byte b)
byte p = 0, high;
for (int i = 0; i &lt; 8; i++)
if ((b &amp; 1) == 1) p ^= a;
high = (a &amp; 0x80);
a «= 1;
if(high == 0x80) a ^= px;
b »= 1;
return p;
משיקולי יעילות רצוי לחשב כפל פולינומים בשדה formula_15 בעזרת רקורסיה של הכפלות בפולינום formula_101 ('02' בייצוג הקסדצימלי), כיוון שמעשית פעולה זו שקולה לאופרטור ההזזה בסיביות (shift left). למשל formula_102 הוא למעשה הזזת המקדמים צעד אחד שמאלה והוספת אפס בהתחלה. בנוסף, אם התוצאה היא ממעלה גבוהה מ-7 כאשר formula_103 שזה אומר שהתרחשה גלישה, יהיה צורך בפעולת חיסור נוספת לצימצום התוצאה לפי כללי החשבון המודולרי בפולינומים. היות שהגלישה קטנה, אפשר להסתפק בחיסור במקום חילוק מודולרי, ולכן מבצעים רק XOR עם הפולינום האי-פריק formula_104. הפעולות הללו נחשבות לפעולות זולות במונחי מחשוב.
לדוגמה הכפל formula_105 בייצוג הקסדצימלי formula_106 שקול בעצם לפעמיים כפל ב-formula_101 (שתי הזזות) וחיבור אחד. לכן תחילה לאחר שתי הזזות מתקבל formula_108 או formula_109 והיות שהתרחשה גלישה יש לצמצם על ידי חיסור formula_110 ואחר כך החיבור formula_111. לסיכום formula_112 (מודולו formula_16).
התרשים משמאל ממחיש בקוד C++ את הפונקציה Gmul המקבלת שני אלמנטים ומחזירה את תוצאת הכפל שלהם בשדה formula_15 מודולו formula_16. כאן צריך להיזהר מהתקפת ערוץ צדדי המודדת את הפרשי הזמן בעת ביצוע הכפל (התנאי הבודק אם הסיבית הנוכחית ב-formula_116 היא 0 או 1 משפיע על זמן החישוב). לכן במימושים בטוחים מופיע קוד ארוך במקצת שעוקף את הבעיה ומבטיח זמן ביצוע אחיד (constant time) ללא תלות בערכן של סיביות formula_116.
כפל במטריצת המצב.
בחלק אחר של הצופן מתייחסים לעמודות מטריצת המצב כאל מקדמים מעל השדה formula_20. הפעולות האריתמטיות מוגדרות ברמת מילים כאשר כל עמודה מייצגת מילה אחת בגודל 32 סיביות המורכבת מארבעה בתים. כל בית נחשב למקדם אחד וארבעתם מייצגים את האלמנטים של הפולינום formula_119. פעולת הכפל דומה לשדה הבינארי, כאשר את הכפל הארוך ממירים לסדרה של הזזות (shift) ברמה של בתים והתוצאה היא השארית מחילוק ב-formula_36, כלומר כל אלמנט הוא תוצאה של formula_121. יוצא שהכפל בפולינום formula_101 או בחזקות שלו מקביל למעשה להזזה מעגלית ברמה של בתים, כלומר לא יותר מאשר הזזה מעגלית של בתים שמאלה כאשר הבית שנפלט מצד שמאל מוחזר מצד ימין. למשל כדי לחשב את הכפל המודולרי formula_123 מחשבים את formula_124 על ידי:
כיוון שהפולינום איתו מכפילים קבוע, אפשר להכין מראש טבלה שמייצגת את ההזזות (צד ימין בתרשים). לדוגמה אם ערכי העמודה הם: formula_125 התוצאה תהיה: formula_126. בהמשך מובא קוד C++ הממחיש את הפעולה.
היבטי יישום.
צופן ריינדל מתאים להטמעה ביעילות במגוון מערכות ובחומרה ייעודית כמו כרטיס חכם המצויד במעבד 8-ביט. במעבד 32 סיביות אפשר למטב את הצופן על ידי המרת ארבע טרנספורמציות הסבב בטבלת חיפוש אחת גדולה. בנוסף, כפל מטריצות ניתן לפישוט על ידי צירוף ליניארי של וקטורים. מכינים ארבע טבלאות formula_127 עד formula_128 המכילות 256 מילים בגודל 4 בתים (סך הכול 4 קילובתים). את ערכי הכניסות מחשבים ומקודדים מראש (החישובים מופיעים בתיאור האלגוריתם על ידי המחברים, ערכי הטבלאות מופיעים באתר NIST בתקן 197) ואז אפשר להמיר את טרנספורמציות הסבב לפעולה הבאה:
formula_129
אפשר לראות שהפעולה מורכבת מארבע פעולות חיפוש בטבלה וארבע פעולות XOR לכל עמודה בכל סבב. במחיר של שלוש הזזות נוספות בכל עמודה (בכל סבב) אפשר להחליף את הפעולה הקודמת בפעולת חיפוש בטבלה אחת בגודל אחד קילוביט:
formula_130
כמו כן כדי לשמור על קוד מצומצם אם נחוץ אפשר להוסיף קוד לייצור הטבלאות בזמן ריצה במקום לקודד אותן מראש. בסבב האחרון בשל העובדה שלא מבוצעת הטרנספורמציה MixColumn לא ניתן להשתמש בטבלאות formula_131 כיוון שהן חושבו כדי לכלול את הטרנספורמציה הזו. במקום ליצור טבלה מיוחדת עבור הסבב האחרון אפשר לבצע מיסוך (masking) כדי לחלץ את הערך הנכון בזמן ביצוע הסבב האחרון.
הצפנה.
void Rijndael(byte *state, byte *expKey, int nRounds)
byte roundKey[16];
createRoundKey(expKey, roundKey);
addRoundKey(state, roundKey);
for (int i = 1; i &lt; nRounds; i++)
createRoundKey(expKey + 16*i, roundKey);
subBytes(state);
shiftRows(state);
mixColumns(state);
addRoundKey(state, roundKey);
createRoundKey(expKey + 16 * nRounds, roundKey);
subBytes(state);
shiftRows(state);
addRoundKey(state, roundKey);
// encryption
int AES_Encrypt(byte *input, byte *output, byte *key, int size)
int nbrRounds;
byte expKey[240]; // the expanded key takes 240 bytes at most
byte block[AES_BLOCK];
switch (size)
case 16: nbrRounds = 10; break;
case 24: nbrRounds = 12; break;
case 32: nbrRounds = 14; break;
default: return -1; // error code for wrong key size
for (int i = 0; i &lt; 4; i++)
for (int j = 0; j &lt; 4; j++)
block[(i + (j * 4))] = input[(i * 4) + j];
expandKey(expKey, key, size, 16 * (nbrRounds + 1));
rijndael(block, expKey, nbrRounds);
for (int i = 0; i &lt; 4; i++)
for(int j = 0; j &lt; 4; j++)
output[(i * 4) + j] = block[(i + (j * 4))];
return 0;
פענוח.
void iRijndael(byte *state, byte *expKey, int nRounds)
byte roundKey[16];
createRoundKey(expKey + 16 * nRounds, roundKey);
addRoundKey(state, roundKey);
for(int i = nRounds - 1; i &gt; 0; i--)
createRoundKey(expKey + 16*i, roundKey);
iShiftRows(state);
iSubBytes(state);
addRoundKey(state, roundKey);
iMixColumns(state);
createRoundKey(expKey, roundKey);
iShiftRows(state);
iSubBytes(state);
addRoundKey(state, roundKey);
// decryption
int AES_Decrypt(byte *input, byte *output, byte *key, int size)
int nbrRounds;
byte expKey[240];
byte block[16];
switch (size)
case 16: nbrRounds = 10; break;
case 24: nbrRounds = 12; break;
case 32: nbrRounds = 14; break;
default: return -1;
for (int i = 0; i &lt; 4; i++)
for(int j = 0; j &lt; 4; j++)
block[(i+(j*4))] = input[(i * 4) + j];
expandKey(expKey, key, size, 16 * (nbrRounds + 1));
inv_rijndael(block, expKey, nbrRounds);
for (int i = 0; i &lt; 4; i++)
for (int j = 0; j &lt; 4; j++)
output[(i*4)+j] = block[(i+(j*4))];
return 0;
ביצועים.