HC-128

HC-128 הוא צופן זרם סינכרוני מותאם במיוחד לתוכנה שפותח ב-2004 על ידי Hongjun Wu אוניברסיטת לוון, הוצע לפרויקט eSTREAM ונבחר כצופן זרם מועדף בקטגוריית תוכנה יחד עם מבחר אלגוריתמים נוספים. הצופן מקבל מפתח בגודל 128 סיביות ווקטור אתחול בגודל 128 סיביות. המצב הפנימי (internal state) של הצופן מורכב משתי טבלאות כל אחת בגודל 512 אוגרים בגודל 32 סיביות. בכל צעד, אוגר אחד באחת הטבלאות מעודכן באמצעות פונקציית הזנה אי-ליניארית והפלט בגודל 32 סיביות נוצר על ידי פונקציית סינון אי-ליניארית. אפשר לייצר זרם מפתח בגודל מרבי של 2 64 {\displaystyle 2^{64}} סיביות. המסר מוצפן ומפוענח באמצעות XOR עם זרם המפתח. מפתח הצופן מציין את ההצהרות הבאות: מחזוריות הצופן היא לכל היותר 2 128 {\displaystyle 2^{128}} . שחזור מפתח ההצפנה קשה כחיפוש ממצה. התקפת הבחנה (distinguishing attack) דורשת לפחות 2 64 {\displaystyle 2^{64}} סיביות מפתח. לא קיימים מפתחות הצפנה חלשים. הצופן אינו מוגן בפטנט כלשהו. מהירות ההצפנה היא בערך 3.05 מחזורי שעון לבית על מחשב פנטיום. HC-128 הוא גרסה פשוטה של צופן HC-256 והוא פשוט ומהיר בתוכנה, מנצל את יכולות המעבד המודרני ומאפשר מקביליות, פונקציית ההזנה ופונקציית הפלט יכולות להתבצע בו זמנית. סימנים מוסכמים: וקטור האתחול I V {\displaystyle IV} והמפתח הסודי K {\displaystyle K} המסופקים על ידי המשתמש כל אחד בגודל 128 סיביות וכן זיכרון בכמות של שתי טבלאות P {\displaystyle P} ו- Q {\displaystyle Q} כל אחת בגודל 512 אלמנטים של 32 סיביות. זרם המפתח המשמש להצפנה נקרא s {\displaystyle s} . והוא שרשור של ערכי 32 סיביות s 0   ‖   s 1   ‖   s 2   ‖   . . .   {\displaystyle s_{0}\ \|\ s_{1}\ \|\ s_{2}\ \|\ ...\ } שמופקים בכל צעד. ישנן 6 פונקציות עזר בצופן. הפונקציות f 1 ( x ) {\displaystyle f_{1}(x)} ו- f 2 ( x ) {\displaystyle f_{2}(x)} שאולות מפונקציות דומות שהן חלק מפונקציית התמצות של SHA-2. לצורך הפונקציה h 1 ( x ) {\displaystyle h_{1}(x)} משתמשים בטבלה Q {\displaystyle Q} כתיבות החלפה (s-box) ועבור הפונקציה h 2 ( x ) {\displaystyle h_{2}(x)} משתמשים בטבלה P {\displaystyle P} . הפונקציות הן: f 1 ( x ) = ( x ⋙ 7 ) ⊕ ( x ⋙ 18 ) ⊕ ( x ≫ 3 ) {\displaystyle f_{1}(x)=(x\ggg 7)\oplus (x\ggg {18})\oplus (x\gg 3)} f 2 ( x ) = ( x ⋙ 17 ) ⊕ ( x ⋙ 19 ) ⊕ ( x ≫ 10 ) {\displaystyle f_{2}(x)=(x\ggg {17})\oplus (x\ggg {19})\oplus (x\gg {10})} g 1 ( x , y , z ) = ( ( x ⋙ 10 ) ⊕ ( x ⋙ 23 ) ) + ( y ⋙ 8 ) {\displaystyle g_{1}(x,y,z)=((x\ggg {10})\oplus (x\ggg {23}))+(y\ggg 8)} g 2 ( x , y , z ) = ( ( x ⋘ 10 ) ⊕ ( z ⋘ 23 ) ) + ( y ⋘ 8 ) {\displaystyle g_{2}(x,y,z)=((x\lll {10})\oplus (z\lll {23}))+(y\lll 8)} h 1 ( x ) = Q [ x 0 ] + Q [ 256 + x 2 ] {\displaystyle h_{1}(x)=Q[x_{0}]+Q[256+x_{2}]} h 2 ( x ) = P [ x 0 ] + P [ 256 + x 2 ] {\displaystyle h_{2}(x)=P[x_{0}]+P[256+x_{2}]} כאשר x = x 0   ‖   x 1   ‖   x 2   ‖   x 3 {\displaystyle x=x_{0}\ \|\ x_{1}\ \|\ x_{2}\ \|\ x_{3}} הוא מילה בגודל 32 סיביות והאלמנטים x i {\displaystyle x_{i}} הם ארבעת הבתים המרכיבים את המילה לפי סדר בתים קטן. כאשר בית אפס מייצג את החלק הפחות משמעותי של המילה. תהליך האתחול של הצופן כולל הרחבת וקטור האתחול והמפתח הסודי לשתי הטבלאות P {\displaystyle P} ו- Q {\displaystyle Q} בדומה ל-SHA-256. ואז מריצים את הצופן 1024 צעדים בהם לא משתמשים בפלט הצופן כמפתח אלא הפלט מוזן בחזרה לעדכון אלמנטים בטבלאות. יהי K = K 0   ‖   K 1   ‖   K 2   ‖   K 3 {\displaystyle K=K_{0}\ \|\ K_{1}\ \|\ K_{2}\ \|\ K_{3}} וכן I V = I V 0   ‖   I V 1   ‖   I V 2   ‖   I V 3 {\displaystyle IV=IV_{0}\ \|\ IV_{1}\ \|\ IV_{2}\ \|\ IV_{3}} כאשר כל K i {\displaystyle K_{i}} ו- I V i {\displaystyle IV_{i}} בגודל 32 סיביות. משכפלים את המפתח ווקטור האתחול ארבע פעמים בניסוח פורמלי: K i + 4 = K i {\displaystyle K_{i+4}=K_{i}} ו- I V i + 1 = I V i {\displaystyle IV_{i+1}=IV_{i}} עבור 0 ≤ i < 4 {\displaystyle 0\leq i<4} . המפתח ווקטור האתחול מורחבים למערך עזר זמני W {\displaystyle W} בגודל 1280 אלמנטים של 32 סיביות כדלהלן: W i = { K i , 0 ≤ i ≤ 7 I V i − 8 , 8 ≤ i ≤ 15 f 2 ( W i − 2 ) + W i − 7 + f 1 ( W i − 15 ) + W i − 16 + i , 16 ≤ i ≤ 1279 {\displaystyle W_{i}={\begin{cases}K_{i},&0\leq i\leq 7\\IV_{i-8},&8\leq i\leq 15\\f_{2}(W_{i-2})+W_{i-7}+f_{1}(W_{i-15})+W_{i-16}+i,&16\leq i\leq 1279\end{cases}}} מעדכנים את הטבלאות כדלהלן: עבור i = 0 {\displaystyle i=0} עד 511 {\displaystyle 511} בצע: P [ i ] = W i + 256 , Q [ i ] = W i + 768 {\displaystyle P[i]=W_{i+256},Q[i]=W_{i+768}} . מריצים את הצופן 1024 צעדים כדלהלן: If  i = 0  to  511  do  {\displaystyle {\text{If }}i=0{\text{ to }}511{\text{ do }}} P [ i ] = ( P [ i ] + g 1 ( P [ i ⊟ 3 ] , P [ i ⊟ 10 ] , P [ i ⊟ 511 ] ) ) ⊕ h 1 ( P [ i ⊟ 12 ] ) {\displaystyle P[i]=(P[i]+g_{1}(P[i\boxminus 3],P[i\boxminus 10],P[i\boxminus 511]))\oplus h_{1}(P[i\boxminus 12])} If  i = 0  to  511  do  {\displaystyle {\text{If }}i=0{\text{ to }}511{\text{ do }}} Q [ i ] = ( Q [ i ] + g 2 ( Q [ i ⊟ 3 ] , Q [ i ⊟ 10 ] , Q [ i ⊟ 511 ] ) ) ⊕ h 2 ( Q [ i ⊟ 12 ] ) {\displaystyle Q[i]=(Q[i]+g_{2}(Q[i\boxminus 3],Q[i\boxminus 10],Q[i\boxminus 511]))\oplus h_{2}(Q[i\boxminus 12])} בשלב זה הצופן מוכן לשימוש. הצפנה ופענוח זהים כמו בכל צופן זרם, תחילה מייצרים זרם מפתח באורך המסר המיועד להצפנה ואז מבצעים XOR מילה אחר מילה של כל המסר עם זרם המפתח. המחולל המייצר את זרם המפתח מעדכן בכל צעד אלמנט אחד מהטבלאות וכן מפיק פלט של 32 סיביות כחלק מזרם המפתח. כל תיבת החלפה מתוך הטבלאות משמשת להפקת 512 פלטים בלבד ולאחר מכן מתעדכנת לצורך 512 הפלטים הבאים. להלן פסאודו קוד המתאר את מהלכי מחולל זרם המפתח בכל צעד או פעימה. שומרים מונה i {\displaystyle i} שמתחיל באפס ומקודם אחרי כל ריצה של המחולל: j = i  mod  512 {\displaystyle j=i{\text{ mod }}512} If  ( i  mod  1024 ) < 512  then do  {\displaystyle {\text{If }}(i{\text{ mod }}1024)<512{\text{ then do }}} P [ j ] = P [ j ] + g 1 ( P [ i ⊟ 3 ] , P [ j ⊟ 10 ] , P [ j ⊟ 511 ] ) {\displaystyle P[j]=P[j]+g_{1}(P[i\boxminus 3],P[j\boxminus 10],P[j\boxminus 511])} s i = h 1 ( P [ j ⊟ 12 ] ) ⊕ P [ j ] {\displaystyle s_{i}=h_{1}(P[j\boxminus 12])\oplus P[j]} Else do  {\displaystyle {\text{Else do }}} Q [ j ] = Q [ j ] + g 2 ( Q [ j ⊟ 3 ] , Q [ j ⊟ 10 ] , Q [ j ⊟ 511 ] ) {\displaystyle Q[j]=Q[j]+g_{2}(Q[j\boxminus 3],Q[j\boxminus 10],Q[j\boxminus 511])} s i = h 2 ( Q [ j ⊟ 12 ] ) ⊕ Q [ j ] {\displaystyle s_{i}=h_{2}(Q[j\boxminus 12])\oplus Q[j]} i = i + 1 {\displaystyle i=i+1} הפלט הוא s i {\displaystyle s_{i}} . חוזרים על תהליך זה שוב ושוב עד שמתקבל זרם מפתח באורך הרצוי, עד לאורך מקסימלי של 2 64 {\displaystyle 2^{64}} סיביות או 2 59 {\displaystyle 2^{59}} מילים. לאחר מכן רצוי להחליף וקטור אתחול או מפתח. פונקציות הפלט וההזנה של הצופן הן בעלי תכונת אי-ליניאריות גבוהה, לכן התקפת קורלציה והתקפה אלגברית אינן ישימות כנגד צופן זה. בנוסף הטבלאות מתעדכנות בזמן ריצה לכן קשה לפתח יחס ליניארי כלשהו בין הקלט לפלט של תיבות ההחלפה. מצב הצופן מכיל בסך הכול 32,778 סיביות, כך שהוא מכיל מחזוריות גבוהה למדי, אם כי קשה להעריכה במדויק. בממוצע מדובר על 2 256 {\displaystyle 2^{256}} סיביות. כן המספר הגדול של מצבי צופן, מסכל התקפת איזון זמן/זיכרון/דטה כנגד צופן זרם. אף על פי שפונקציית הפלט האי-ליניארית מדליפה מעט מידע בכל צעד, פונקציית ההזנה מבטיחה שלא ניתן יהיה לנצל מידע זה כדי לשחזר את המפתח הסודי. בשלב האתחול הושם דגש על פיזור טוב והשפעה של סיביות המפתח ווקטור האתחול על כל סיביות טבלאות המצב הפנימי, כדי למנוע התקפה דיפרנציאלית. 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 (הפונקציה הושמטה כאן מטעמי חסכון מקום). HC-128