פרימיטיבים וממשקים

בשלב הבא אנחנו מגדירים (באופן לא רשמי, אבל בצורה יותר רשמית) שתי חלקים חשובים של השפה שבה משתמשים ב-Tink, Primitive ו-Interface.

פרימיטיבי

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

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

ממשקים

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

הערה: כאן יש הבחנה ברורה בין הממשקים והפרימיטיבים. כך צריך להבהיר שהאובייקט המתמטי שאליו שני הממשקים נותנים גישה זהה.

הגדרות רשמיות

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

פונקציות קריפטוגרפיות

המושג 'פונקציה קריפטוגרפית' לא חשוב כמו המושג של פרימיטיב, אבל צריך להציג אותו כדי להגדיר באופן רשמי את 'פרימיטיב'.

פונקציה קריפטוגרפית

פונקציה קריפטוגרפית היא מפה

\[ f: {\bf K} \times {\bf R} \times {\bf I} \to {\bf O}\]

מסדרה \({\bf K}\) (מרחב המקשים), קבוצה \({\bf R} = \{0,1\}^{\infty}\)(אקראיות, שאנחנו מניחים שהיא קבוצה של מחרוזות סיביות אינסופיות), וקבוצה \({\bf I}\) (מרחב הקלט), לקבוצה \({\bf O}\) (מרחב הפלט).

בהמשך יהיה ברור למה הוספנו פרמטר אקראי ספציפי.

לדוגמה, אנחנו מציגים אפשרות אחת כדי למפות את המושגים האלה ל-AES-GCM. לכל גודל מפתח חוקי \(s_k\), גודל חד-פעמי \(s_n\)וגודל תג\(s_t\), AES-GCM מכיל שתי פונקציות קריפטוגרפיות, אחת להצפנה ואחת לפענוח. בשניהם יהיה אותו מרחב מקשים \({\bf K} = \{0,1\}^{s_k}\).

בפונקציית ההצפנה \(\mathrm{Enc}\), הסיביות הרנדומליות \(s_n\) הראשונות ישמשו לבחירת הצופן החד-פעמי (nonce).

תן \({\bf B} = \{0,1\}^8\) לציין בייט. מרחב הקלט של פונקציית ההצפנה הוא צמדים \({\bf I} = {\bf B}^{*} \times {\bf B}^{*}\) של צמדים של מחרוזות בייטים באורך שרירותי. הרכיב הראשון של הצמד נועד לשמש כמסר, הרכיב השני הוא הנתונים המשויכים. לתקן AES-GCM יש מגבלה עליונה על האורך של ערכי הקלט, אבל אנחנו מעדיפים לאפשר אורכים שרירותיים ובמקום זאת להוסיף סמל שגיאה מיוחד \(\bot\) למרחב הפלט. מרחב הפלט הופך ל- \({\bf O} = {\bf B}^* \cup \{\bot\}\), שבו אנחנו מגדירים באופן שרירותי את התוצאה של חישובים מצליחים כשרשור \((\mathrm{IV} \| \mathrm{ciphertext} \| \mathrm{tag})\) כפי שנקבע בתקן, ובפלט\(\bot\), במקרה שחלק מהקלט ארוך מדי. לכן, למפתח קבוע, פונקציית ההצפנה הופכת לסוג \(\mathrm{Enc}_k : {\bf R} \times {\bf B}^* \times {\bf B}^* \rightarrow {\bf B}^* \cup \{\bot\}\).

בפונקציית הפענוח \(\mathrm{Dec}\) שטח המפתח זהה. מרחב הקלט במקרה זהה: \({\bf I} ={\bf B}^* \times {\bf B}^*\), אבל עכשיו הרכיב הראשון אמור לשמש כפלט של פונקציית ההצפנה, והרכיב השני הוא עדיין הנתונים שמשויכים אליו.

גם מרחב הפלט הוא אותו \({\bf O} = {\bf B}^* \cup \{\bot\}\) (שוב, במקרה). הפרשנות שונה במקצת, מכיוון ש- \(\bot\) בדרך כלל מציין שגיאת אימות (למרות שהיא תשמש גם כפלט במקרה של קלט ארוך מדי).

אנחנו מדגישים שתהליך היצירה שלמעלה הוא לא הדרך היחידה ליצור את הסטנדרט. לדוגמה, אפשר להחשיב את החלק החד-פעמי (nonce) בקלט, במקום לקרוא אותו לפי הרנדומיזציה (והתוצאה היא פרימיטיב שונה מאוד). לחלופין, אפשר להגדיר את הפלט כשלשה שמכילה את ה-nonce, את המידע מוצפן (ciphertext) ואת התג (במקום את השרשור). לחלופין, אפשר להגביל את השטח של המפתח (באופן שרירותי במידה מסוימת) ל-\({\bf K} = \{0,1\}^{128} \cup \{0,1\}^{256}\).

אלגוריתם קריפטוגרפי:

אלגוריתם קריפטוגרפי (סימטרי) הוא מודל קריפטוגרפי

\[(f_1, ... f_k)\]

של פונקציות קריפטוגרפיות, שבהן לכל הפונקציות יש אותו מרחב מפתח. הסוג של האלגוריתם הקריפטוגרפי הוא ה-tuple \((({\bf I}_1, {\bf O}_1), \ldots, ({\bf I}_k, {\bf O}_k))\).

לדוגמה, לכל שלשה תקינה \((s_k, s_n, s_t)\) של מפתח, חד-פעמי (nonce) וגודל תג, AES-GCM\({}_{s_k, s_n, s_t}\) הוא אלגוריתם קריפטוגרפי שכולל את שתי הפונקציות \(\mathrm{Enc}\) ו \(\mathrm{Dec}\) המתוארות למעלה.

פרימיטיבים וממשקים

בשלב הבא נגדיר פרימיטיב קריפטוגרפי.

פרימיטיבי
פרימיטיב הוא קבוצה של אלגוריתמים קריפטוגרפיים, שבה לכל האלגוריתמים יש אותו סוג \((({\bf I}_1, {\bf O}_1), \ldots, ({\bf I}_k, {\bf O}_k))\), והרווחים העיקריים של האלגוריתמים נפרדים זה מזה.

לדוגמה, נחשוב על \(\mathrm{AEAD}\) הפרימיטיבי ב-Tink. יש לו מספר אלגוריתמים, ביניהם AES-GCM לגדלים של 128 ו-256 ביטים, עם גודל חד-פעמי של 96 ביטים, AES-EAX עם גדלים מסוימים של מפתחות ו-XChaCha20Poly1305. יש בהם מרחבי מפתח נפרדים, אבל כולן מציעות פונקציות קריפטוגרפיות זהות\(\mathrm{Enc}\) ו- \(\mathrm{Dec}\). (אנחנו לא רואים מטרה כלשהי לכיווץ גדלים שונים של AES-GCM בדיון הרשמי הזה, אבל ברור שזאת הסיבה לכך).

הגדרת פרימיטיבים

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

לדוגמה, עבור AEAD נגיד ש \(\mathrm{Dec}_k(\mathrm{Enc}_k(m, a), a) = m\) המשמעות היא 'תמיד' (מלבד במקרה שהטקסט ללא הצפנה \(m\) ארוך מדי). בנוסף, יש לנו מאפייני אבטחה. לדוגמה, למפתח אקראי ההצפנה מאובטחת מבחינה סמנטית.

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

ממשקים

ממשק ב-Tink נותן גישה לפרימיטיב, מהבחינה שהוא מאפשר לחשב רכיב של מרחב הפלט ממרחב הקלט. לדוגמה, נתייחס לממשק AEAD ב-Java:

public interface Aead {
  byte[] encrypt(byte[] plaintext, byte[] associated_data) throws GeneralSecurityException;
  byte[] decrypt(byte[] ciphertext, byte[] associated_data) throws GeneralSecurityException;
}

שימו לב שאנחנו לא נותנים גישה לאקראיות. במקום זאת, אנחנו מאפשרים למשתמש לספק רכיבים של מרחב הקלט. חסימת הגישה לאקראיות היא מכוונת בכוונה.1

לפעמים ספריית Tink מציעה מספר ממשקים לפרימיטיב אחד. השיטה הזו יכולה להיות שימושית מאוד, כי לפעמים הדרישות שונות. עם זאת, לפעולה הזו יש מחיר: באופן כללי, ככל שיש יותר ממשקים, כך פחות יכולת פעולה הדדית. לדוגמה, נניח שמשתמש כותב ספרייה על בסיס Tink שבה המשתמשים צריכים להעביר אובייקט Aead (כדי להצפין מידע פנימי). אם ספריית Tink מציעה יותר מדי ממשקים \(\mathrm{AEAD}\) פרימיטיביים, רוב הסיכויים שלמשתמש אין מכונה שאפשר להשתמש בה בו-זמנית עם המפתח שהמשתמש בחר. לכן, הוספת עוד ממשקים לא כדאי לפספס.


  1. לצפנות AEAD יש את המאפיין מאובטח מפני מתקפות נבחרות של מידע מוצפן (ciphertext), מובטח רק אם לא ייעשה שימוש חוזר בצופן החד-פעמי (ciphertext). ממשק Aead ב-Tink תוכנן כך שמונע שימוש חוזר חד-פעמי: המשתמש לא יכול לספק חד-פעמיות (nonce) כקלט להצפנה. במקום זאת, נוצר חד-פעמי חדש באופן אקראי לכל פעולת הצפנה.