HC-128

HC-128 הוא צופן זרם סינכרוני מותאם במיוחד לתוכנה שפותח ב-2004 על ידי Hongjun Wu אוניברסיטת לוון, הוצע לפרויקט eSTREAM ונבחר כצופן זרם מועדף בקטגוריית תוכנה יחד עם מבחר אלגוריתמים נוספים. הצופן מקבל מפתח בגודל 128 סיביות ווקטור אתחול בגודל 128 סיביות. המצב הפנימי (internal state) של הצופן מורכב משתי טבלאות כל אחת בגודל 512 אוגרים בגודל 32 סיביות. בכל צעד, אוגר אחד באחת הטבלאות מעודכן באמצעות פונקציית הזנה אי-ליניארית והפלט בגודל 32 סיביות נוצר על ידי פונקציית סינון אי-ליניארית. אפשר לייצר זרם מפתח בגודל מרבי של  סיביות. המסר מוצפן ומפוענח באמצעות XOR עם זרם המפתח.

מפתח הצופן מציין את ההצהרות הבאות: מחזוריות הצופן היא לכל היותר . שחזור מפתח ההצפנה קשה כחיפוש ממצה. התקפת הבחנה (distinguishing attack) דורשת לפחות  סיביות מפתח. לא קיימים מפתחות הצפנה חלשים. הצופן אינו מוגן בפטנט כלשהו. מהירות ההצפנה היא בערך 3.05 מחזורי שעון לבית על מחשב פנטיום.

HC-128 הוא גרסה פשוטה של צופן HC-256 והוא פשוט ומהיר בתוכנה, מנצל את יכולות המעבד המודרני ומאפשר מקביליות, פונקציית ההזנה ופונקציית הפלט יכולות להתבצע בו זמנית.

תיאור הצופן
סימנים מוסכמים:

וקטור האתחול  והמפתח הסודי  המסופקים על ידי המשתמש כל אחד בגודל 128 סיביות וכן זיכרון בכמות של שתי טבלאות  ו- כל אחת בגודל 512 אלמנטים של 32 סיביות. זרם המפתח המשמש להצפנה נקרא . והוא שרשור של ערכי 32 סיביות   שמופקים בכל צעד.

ישנן 6 פונקציות עזר בצופן. הפונקציות  ו- שאולות מפונקציות דומות שהן חלק מפונקציית התמצות של SHA-2. לצורך הפונקציה  משתמשים בטבלה  כתיבות החלפה (s-box) ועבור הפונקציה  משתמשים בטבלה . הפונקציות הן:

כאשר  הוא מילה בגודל 32 סיביות והאלמנטים  הם ארבעת הבתים המרכיבים את המילה לפי סדר בתים קטן. כאשר בית אפס מייצג את החלק הפחות משמעותי של המילה.

תהליך אתחול
תהליך האתחול של הצופן כולל הרחבת וקטור האתחול והמפתח הסודי לשתי הטבלאות  ו- בדומה ל-SHA-256. ואז מריצים את הצופן 1024 צעדים בהם לא משתמשים בפלט הצופן כמפתח אלא הפלט מוזן בחזרה לעדכון אלמנטים בטבלאות.
יהי  וכן  כאשר כל  ו- בגודל 32 סיביות. משכפלים את המפתח ווקטור האתחול ארבע פעמים בניסוח פורמלי:  ו- עבור . המפתח ווקטור האתחול מורחבים למערך עזר זמני  בגודל 1280 אלמנטים של 32 סיביות כדלהלן:

מעדכנים את הטבלאות כדלהלן: עבור  עד  בצע: . מריצים את הצופן 1024 צעדים כדלהלן:

בשלב זה הצופן מוכן לשימוש.

הצפנה ופענוח
הצפנה ופענוח זהים כמו בכל צופן זרם, תחילה מייצרים זרם מפתח באורך המסר המיועד להצפנה ואז מבצעים XOR מילה אחר מילה של כל המסר עם זרם המפתח. המחולל המייצר את זרם המפתח מעדכן בכל צעד אלמנט אחד מהטבלאות וכן מפיק פלט של 32 סיביות כחלק מזרם המפתח. כל תיבת החלפה מתוך הטבלאות משמשת להפקת 512 פלטים בלבד ולאחר מכן מתעדכנת לצורך 512 הפלטים הבאים. להלן פסאודו קוד המתאר את מהלכי מחולל זרם המפתח בכל צעד או פעימה. שומרים מונה  שמתחיל באפס ומקודם אחרי כל ריצה של המחולל:

הפלט הוא . חוזרים על תהליך זה שוב ושוב עד שמתקבל זרם מפתח באורך הרצוי, עד לאורך מקסימלי של  סיביות או  מילים. לאחר מכן רצוי להחליף וקטור אתחול או מפתח.

ביטחון
פונקציות הפלט וההזנה של הצופן הן בעלי תכונת אי-ליניאריות גבוהה, לכן התקפת קורלציה והתקפה אלגברית אינן ישימות כנגד צופן זה. בנוסף הטבלאות מתעדכנות בזמן ריצה לכן קשה לפתח יחס ליניארי כלשהו בין הקלט לפלט של תיבות ההחלפה. מצב הצופן מכיל בסך הכול 32,778 סיביות, כך שהוא מכיל מחזוריות גבוהה למדי, אם כי קשה להעריכה במדויק. בממוצע מדובר על  סיביות. כן המספר הגדול של מצבי צופן, מסכל התקפת איזון זמן/זיכרון/דטה כנגד צופן זרם.

אף על פי שפונקציית הפלט האי-ליניארית מדליפה מעט מידע בכל צעד, פונקציית ההזנה מבטיחה שלא ניתן יהיה לנצל מידע זה כדי לשחזר את המפתח הסודי. בשלב האתחול הושם דגש על פיזור טוב והשפעה של סיביות המפתח ווקטור האתחול על כל סיביות טבלאות המצב הפנימי, כדי למנוע התקפה דיפרנציאלית.

קוד לדוגמה
typedef struct { 
      uint P[512];
      uint Q[512];
      uint counter1024;     // counter1024 = i mod 1024
      uint keystreamword;
} HC128_State;

#define ROTR32(x,n)   ( ((x) >> (n))  | ((x) << (32 - (n))) )
#define ROTL32(x,n)   ( ((x) << (n))  | ((x) >> (32 - (n))) )

#define f1(x)    (ROTR32((x),7) ^ ROTR32((x),18) ^ ((x) >> 3))
#define f2(x)    (ROTR32((x),17) ^ ROTR32((x),19) ^ ((x) >> 10))

uint h1(HC128_State *state, uint u) {
      return state->Q[u]+state->Q[256+(u >> 16)];
}

uint h2(HC128_State *state, uint u) {	
      return state->P[u]+state->P[256+(u >> 16)];
}

void OneStep(HC128_State *state) 
{
      uint i   = state->counter1024 & 0x1ff;
      uint i3  = (i - 3) & 0x1ff;
      uint i10 = (i - 10) & 0x1ff;
      uint i12 = (i - 12) & 0x1ff;
      uint i511 = (i - 511) & 0x1ff;

      if (state->counter1024 < 512) {
            state->P[i] = state->P[i] + g1(state->P[i3], state->P[i10], state->P[i511]);
            state->keystreamword = h1(state, state->P[i12]) ^ state->P[i];
            // state->P[i] = h1(state,state->P[i12]) ^ state->P[i]; // replace with this for init
      }
      else {
            state->Q[i] = state->Q[i] + g2(state->Q[i3], state->Q[i10], state->Q[i511]);
            state->keystreamword = h2(state, state->Q[i12]) ^ state->Q[i];
            // state->Q[i] = h2(state,state->Q[i12]) ^ state->Q[i]; // replace with this for init
      }
      state->counter1024 = (state->counter1024+1) & 0x3ff;
}

void Initialization(HC128_State *state, byte *key, byte *iv) 
{
      uint W[1024+256],i;

      for (i = 0; i < 4; i++) {
          W[i] = ((uint*)key)[i]; W[i+4] = ((uint*)key)[i];
      }
      for (i = 0; i < 4; i++) {
          W[i+8] = ((uint*)iv)[i]; W[i+12] = ((uint*)iv)[i];
      }

      for (i = 16; i < 1024+256; i++) 
         W[i] = f2(W[i-2]) + W[i-7] + f1(W[i-15]) + W[i-16]+i;

      for (i = 0; i < 512; i++)  state->P[i] = W[i+256];
      for (i = 0; i < 512; i++)  state->Q[i] = W[i+256+512];

      state->counter1024 = 0;

      for (i = 0; i < 1024; i++)  InitOneStep(state); // see comment
}

void EncryptMessage(HC128_State *state, byte *message, byte *ciphertext, long msglength)
{
      uint64 i;
      uint j;
      
      for (i = 0; (i+4) <= msglength; i += 4, message += 4, ciphertext += 4) 
      {
            OneStep(state);   
            ((uint*)ciphertext)[0] = ((uint*)message)[0] ^ state->keystreamword;   
      }
 
      if ((msglength & 3) != 0)
      {
            OneStep(state);
            for (j = 0; j < (msglength & 3); j++) {
                  *(ciphertext+j) = *(message+j) ^ ((byte*)&state->keystreamword)[j];
            }
      }    
}

הערה: הפונקציה OneStep בשלב האתחול זהה למעט שתי שורות המסומנות בהערה, יש להחליף את השורות הללו בשורות שלפניהן ולקרוא לפונקציה בהתאם InitOneStep (הפונקציה הושמטה כאן מטעמי חסכון מקום).

הערות שוליים

צפני זרם