אבטחה · לעומק
מחזור החיים הקריפטוגרפי המלא
סקירה מדויקת של מה שקורה למידע שלכם בכל שלב – עם הרכיבים הקריפטוגרפיים, הפרמטרים וגבולות האמון, בפירוט. כל שלב נפתח בסיכום בשפה פשוטה, כך שאפשר לקרוא בכל רמת עומק שרוצים.
שלב 01 · הצפנה
הסוד שלכם נחתם במכשיר שלכם
במילים פשוטות: הערבול מתבצע בדפדפן שלכם, לפני שמשהו מועלה. מה שאנחנו מקבלים כבר אי אפשר לקרוא.
כשלוחצים על הצפנת הסוד, הדפדפן מגריל מפתח תוכן חדש של 256 ביט מ-WebCrypto CSPRNG ומצפין את המטען ב-AES-256-GCM. GCM היא הצפנה מאומתת: כל מקטע נושא תג של 128 ביט, כך שכל שינוי מאוחר מתגלה ולא מפוענח בשקט.
טקסט וקבצים מטופלים באופן זהה – שניהם הופכים לגוש בינארי אטום אחד, מרופד לגודל סטנדרטי, כך שסיסמה קצרה וקובץ קטן נראים אותו דבר. הטקסט הגלוי אף פעם לא נוגע ברשת ואף פעם לא יוצא מהזיכרון של הלשונית.
שלב 02 · החזקת המפתח
המפתח מוסתר בקישור, לא בשרתים שלנו
במילים פשוטות: החלק של הקישור שאחרי ה-# מכיל את המפתח – ודפדפנים אף פעם לא שולחים את החלק הזה לשום שרת.
הקישור שלכם נושא זרע (seed) אקראי של 256 ביט ב-fragment של הכתובת – כל מה שאחרי ה-#. לפי מפרט ה-URL, הדפדפן מסיר את ה-fragment, והוא אף פעם לא נכלל בבקשה שנשלחת לשרתים שלנו. מהזרע הזה הדפדפן גוזר, באמצעות HKDF, ערך חיפוש שבעזרתו מאתרים את הסוד, ואת המפתח שפותח את עטיפת מפתח התוכן. השרת רואה רק גיבוב של ערך החיפוש.
הדפדפן של הנמען קורא את הזרע מה-fragment ומבצע את אותה גזירה מקומית. זו המשמעות של ״ללא ידיעה״ (zero-knowledge) כאן: החזקה בטקסט המוצפן לבדו חסרת תועלת בלי הקישור.
שלב 03 · סיסמה אופציונלית
מנעול שני שרק בני אדם מחזיקים
במילים פשוטות: אם מוסיפים סיסמה, גם מי שמחזיק בקישור המלא לא יכול לפתוח את הסוד בלעדיה.
אם הגדרתם סיסמה, היא עוברת מתיחה ב-Argon2id – פונקציית גזירת מפתחות עתירת זיכרון – ומשולבת עם הזרע למפתח שעוטף את מפתח התוכן. הסיסמה עצמה אף פעם לא משודרת או נשמרת; רק אתם והנמען יודעים אותה. ניחושים שגויים מנוסים שוב בדפדפן של הנמען, מול מידע שכבר נמצא אצלו, כך שהשרת לא יכול לספור אותם.
עתירות הזיכרון הופכת ניחוש לא מקוון בהיקף גדול ליקר, גם על GPU ו-ASIC. שתפו את הסיסמה בערוץ אחר מזה של הקישור, ושני הגורמים לעולם לא ייפגשו במקום אחד. מודול Argon2 נטען בכל יצירה ובכל חשיפה, כך שהרשת לא יכולה לדעת אילו סודות משתמשים בו.
שלב 04 · העברה ואחסון
רק טקסט מוצפן יוצא מהמכשיר שלכם
במילים פשוטות: אנחנו שומרים קופסה נעולה וזמן תפוגה. ותו לא – בלי טקסט גלוי, בלי מפתח, בלי שום תיעוד עליכם.
הגוש מועלה על גבי TLS ונשמר במנוחה כטקסט מוצפן, לצד פרטי גישה מגובבים, משך החיים שלו וזמן תפוגה מעוגל. בכוונה איננו שומרים שום זהות של שולח, נמען, יומן IP, זמן יצירה או היסטוריית גישה. אין מערכת חשבונות שתקשר סוד לאדם. הגבלת הקצב פועלת על HMAC של הכתובת עם salt שמתחלף, כך ששום דבר מלפני ההחלפה לא ניתן לקשר לשום דבר שאחריה.
מכיוון שהאובייקט השמור חסר משמעות בלי הזרע שב-fragment, פריצה לשרת או צו בית משפט יניבו רק בתים אטומים וגיבובים שאי אפשר להפוך בחזרה לשום דבר.
שלב 05 · קבלה והשמדה
הקריאה הראשונה זוכה – ואז הוא נמחק
במילים פשוטות: פתיחת קישור חד־פעמי מוסרת את הקופסה פעם אחת ומוחקת את העותק שבשרת. או שאפשר להשמיד אותו בעצמכם, מתי שרוצים.
שום דבר לא נטען כשדף החשיפה נפתח: הבקשה יוצאת רק בלחיצה או בהקשה אמיתית, כך שסורקי קישורים ותצוגות מקדימות בצ׳אט לא יכולים לצרוך סוד. בסוד חד־פעמי השרת תובע את השורה בעדכון מותנה יחיד – בדיוק בקשה אחת זוכה – והסוד נחשב כנצרך מאותו רגע. אותו דפדפן יכול להוריד שוב במשך 15 דקות אם החיבור נופל; אף אחד אחר לא יכול, ותביעה שלא אושרה מושמדת, אף פעם לא מבוטלת. סודות מוגבלים בזמן הם קריאה רגילה עד שיפוג תוקפם.
לכל סוד יש גם קישור השמדה פרטי. פתיחתו לבדה לא משמידה דבר; האישור מוחק את הטקסט המוצפן מיד – לפני שנמען כלשהו יוכל לקרוא אותו.
מודל איומים
מה shhnote יכול ולא יכול לראות
הגרסה הכנה. כאן בדיוק עובר גבול האמון.
אף פעם לא יוצא מהמכשיר שלכם
מה שאנחנו לא יכולים לראות
הטקסט הגלוי שלכם · מפתח התוכן והזרע · הסיסמה שלכם · האם הוגדרה סיסמה · מי יצר או פתח סוד · מתי הוא נוצר · יומני גישה ברמת IP. שום דבר מאלה לא משודר או נשמר.
נשמר בשרתים שלנו
מה שכן נמצא אצלנו
גוש מוצפן שאיננו יכולים לפתוח · קבוצת הגודל המרופדת שלו · גיבובים חד־כיווניים של הערכים שהדפדפן שלכם משתמש בהם כדי למצוא את הסוד או להשמיד אותו · האם הוא חד־פעמי או מוגבל בזמן · זמן תפוגה מעוגל. כל זה חסר תועלת בלי הזרע שבקישור שלכם.
כדאי לדעת
ההסתייגויות הכנות
כל מי שמחזיק בקישור המלא (ובסיסמה, אם הוגדרה) יכול לקרוא סוד חד־פעמי – התייחסו לקישור כאל הסוד עצמו. אחרי שסוד מוצג, הדף דורס את מה שהוא יכול כשעוזבים אותו, אבל JavaScript לא יכול להבטיח זאת; סגרו את הלשונית כשסיימתם. דפדפן שמופעל באוטומציה שמזריקה קלט אמיתי יכול לפתוח סוד חד־פעמי בדיוק כמו אדם – ואז הנמען לא מוצא כלום, וזו התוצאה הכנה. מחיקת סוד מסירה את הרשומה; שאריות מוצפנות עלולות להישאר ביומנים, בשטח הפנוי בדיסק או בגיבויים עד שיידרסו, כטקסט מוצפן ללא מפתח. סודות גדולים שנשמרים מחוץ למסד הנתונים נושאים את הזמן שבו הקובץ נכתב. וכמו בכל אפליקציית רשת, אתם סומכים על הקוד שנשלח לדפדפן שלכם – ובגלל זה shhnote הוא קוד פתוח וניתן לאירוח עצמי.
מפרט קריפטוגרפי
כל הרכיבים, במקום אחד
הצפנה
הצפנה סימטרית מאומתת במקטעים של 64 KiB. מפתח של 256 ביט ותג שלמות לכל מקטע, כך ששינוי או קיטוע מתגלים – ולא רק הופכים את המידע לבלתי קריא.
גזירת מפתחות
מפתחות ומזהים נגזרים מהזרע באמצעות HKDF-SHA-256. סיסמאות אופציונליות עוברות מתיחה ב-KDF עתיר זיכרון, מה שהופך כוח גס לא מקוון לאיטי ויקר, גם על חומרה ייעודית.
אקראיות
זרעים, מפתחות, ערכי salt ו-nonces מגיעים מהמחולל המאובטח קריפטוגרפית של הדפדפן. אף פעם לא Math.random(), אף פעם לא צפוי.
החזקת המפתח
הזרע נמצא אחרי ה-# בקישור שלכם. דפדפנים אף פעם לא משדרים את ה-fragment, ולכן הוא לא מגיע לשרתים שלנו.
בהעברה
רק טקסט מוצפן יוצא מהמכשיר שלכם, על גבי TLS מודרני. מה שאנחנו מקבלים הוא גוש אטום שאיננו יכולים לפתוח.
באחסון
אנחנו שומרים גוש מוצפן, גיבובים חד־כיווניים של פרטי הגישה שלו וזמן תפוגה מעוגל – בלי טקסט גלוי, בלי מפתחות, בלי מטא־נתונים על השולח או על הנמען.
שאלות טכניות
למי שאבטחה חשובה לו
איך נאכפת ההבטחה החד־פעמית מול מצבי מרוץ (race conditions)?
החשיפה מריצה עדכון מותנה יחיד שמעביר את השורה ממצב "פעיל" למצב "נתבע". שתי בקשות בו־זמניות לא יכולות להצליח שתיהן; השנייה רואה רשומה שכבר נתבעה ומקבלת את אותה תשובת "לא נמצא סוד" כמו כל אחד אחר. התביעה אף פעם לא מבוטלת: אם ההורדה אבדה, מי שמחזיק באסימון התביעה מוריד שוב בתוך חלון הזמן, ואחרת הסוד מושמד בלי שנקרא.
למה ה-fragment של הכתובת ולא פרמטר שאילתה?
פרמטרי שאילתה נשלחים לשרת ונרשמים ביומני גישה, בשרתי פרוקסי ובכלי אנליטיקה. ה-fragment (אחרי #) מוגדר במפרט ה-URL כחלק שנשאר בצד הלקוח – הדפדפן אף פעם לא מכניס אותו לבקשת ה-HTTP. התכונה הזאת היא כל הבסיס של התכנון ללא ידיעה (zero-knowledge). אחרי החשיפה, הדף גם מסיר את ה-fragment משורת הכתובת ומההיסטוריה.
מה מונע מסורקי קישורים לשרוף סודות חד־פעמיים מוקדם מדי?
דף הצפייה הוא אותו מסמך סטטי לכל סוד, והוא לא טוען כלום בזמן הטעינה. הבקשה נשלחת רק בלחיצה או בהקשה אמיתית ומהימנה – click() מתוך סקריפט או שליחת טופס לא נחשבים. סורקים אוטומטיים (אבטחת אימייל, תצוגות מקדימות בצ׳אט, אנטי־וירוס) טוענים רק את המעטפת הסטטית הזאת.
אפשר לאמת את זה או לארח בעצמי?
כן. קוד המקור המלא פתוח לציבור ברישיון AGPLv3, הפרוטוקול מתועד במפרט עם וקטורי תאימות שכל מימוש יכול להיבדק מולם, ויש מדריך לאירוח עצמי. אפשר לבדוק את הקריפטוגרפיה בצד הלקוח ולהפעיל מופע משלכם, כך שגבול האמון יהיה כולו בידיים שלכם.
האם משהו נרשם לצורך מניעת שימוש לרעה?
איננו שומרים יומני גישה לכל סוד או היסטוריית IP. הגבלת הקצב משתמשת בגיבוב עם מפתח של הכתובת, עם salt שמתחלף ומאפס איתו כל מונה. דיווחים על שימוש לרעה נספרים לפי יום; אין תיעוד של איזה סוד דווח.
טיפים לשיתוף בטוח
- שלחו את הסיסמה בדרך אחרת מהקישור, למשל את הקישור באימייל ואת הסיסמה בטלפון.
- בחרו את זמן התפוגה הקצר ביותר שמתאים לכם.
- השתמשו בסוד חד־פעמי לסיסמאות ולמידע אחר שצריך להיקרא פעם אחת בדיוק.
דיווח על חולשת אבטחה
אם נראה לכם שמצאתם בעיית אבטחה באתר הזה, כתבו לנו לכתובת [email protected] וספרו לנו באופן פרטי לפני שאתם מספרים לאחרים. פרטו מה מצאתם ואיך לשחזר את הבעיה.
אנחנו:
- נשיב תוך 5 ימי עבודה;
- נעדכן אתכם בזמן שאנחנו מתקנים;
- ניתן לכם קרדיט כשנפרסם את התיקון, אם תרצו.
בזמן הבדיקות, אנא אל תיגשו למידע של אנשים אחרים, אל תשבשו את השירות ואל תריצו סריקות אוטומטיות שמעמיסות עליו.
חולשות בתוכנת shhnote עצמה: ראו את מדיניות האבטחה של הפרויקט.
נסו את זה – זה לוקח עשר שניות
כתבו משהו סודי, קבלו קישור חד־פעמי, ותראו אותו נעלם.