כלי פרטיות חינמי

איטום קובץ

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

רץ כולו בלשונית הזו. העמוד הזה לא יכול לדבר עם שום שרת בכלל.

כל פרמטר, מוצהר

צופן
AES-256-GCM (WebCrypto), תג בן 128 ביט לכל מקטע
מפתח
256 ביט מה-CSPRNG של הדפדפן, נוצר טרי לכל איטום, בשימוש פעם אחת, לא נגזר משום דבר
מקטוע
מניפסט מוצפן (שם קובץ, גודל, סוג), ואז הקובץ במקטעים של 1 MiB — כל אחד מאומת בנפרד
נונס
מלח איטום אקראי בן 32 ביט ‖ מונה מקטעים בן 64 ביט — לעולם לא חוזר בתוך קובץ, ובלאו הכי המפתח חד-פעמי
נתונים מאומתים
הכותרת המלאה בת 16 הבתים בתוספת האינדקס של כל מקטע וסך כל המקטעים — שינוי סדר, קטיעה או שרבוב של מקטעים שוברים את הפענוח
מחרוזת המפתח
base64url, 47 תווים: המפתח בן 32 הבתים בתוספת סכום ביקורת SHA-256 בן 3 בתים, כך ששגיאת הקלדה נכשלת בצורה נקייה במקום לייצר זבל

תשובות כנות על מה שזה עושה

הכול קורה בדפדפן שלכם.

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

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

המפתח הוא הכול — ואצלנו הוא לעולם לא נמצא.

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

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

קישור המפתח לעולם לא מגיע לשרת.

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

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

אתם רואים מה יש בפנים לפני שנשמר משהו.

הפתיחה מתרחשת בשני שלבים. תחילה העמוד מפענח רק מניפסט קטן: שם הקובץ המקורי, הגודל והסוג. הוא מציג לכם את זה, עם אזהרה בולטת לסוגי קבצים שיכולים להריץ קוד (תוכניות, סקריפטים, מתקינים, מסמכי מאקרו, HTML) ולשמות שמחופשים בסיומת כפולה כמו invoice.pdf.exe. שום דבר לא נכתב עד שתלחצו כדי לפענח. העמוד לעולם לא פותח או מציג בעצמו את התוכן המפוענח. הוא רק מציע את הקובץ להורדה.

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

איפה גבול האמון באמת עובר.

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

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

מגבלות בשפה פשוטה

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

הפורמט, בית אחר בית

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

bytesfieldcontents
0–7magicASCII "SEALBY-W"
8גרסה0x01 — כל דבר אחר נדחה כ״נוצר בגרסה חדשה יותר״
9אלגוריתם0x01 = AES-256-GCM
10–13מלח איטום4 בתים, CSPRNG, טרי לכל איטום
14–15שמורחייב להיות אפס
16–סוףמסגרות מקטעיםכל אחת: אורך הטקסט המוצפן כ-uint32 BE, ואז הטקסט המוצפן ב-AES-GCM + תג בן 16 בתים

מקטע 0 הוא המניפסט: JSON ב-UTF-8 {"name":…,"size":…,"type":…}. מקטעים 1…N−1 הם הקובץ בחתיכות של 1 MiB. עבור מקטע i מתוך N: נונס = מלח ‖ uint64 BE i; נתונים מאומתים = כותרת ‖ uint64 BE i ‖ uint64 BE N. כך ששינוי, קטיעה, שינוי סדר, העברה למיקום אחר ושרבוב בין קבצים כולם נכשלים באימות. כל מקרה מוכח בבדיקה שלילית שהוטמעה.

מחרוזת המפתח: base64url (בלי ריפוד) של המפתח בן 32 הבתים ואחריו 3 הבתים הראשונים של ה-SHA-256 שלו, 47 תווים בסך הכול. שגיאת הקלדה נכשלת בסכום הביקורת במקום לייצר זבל.

מימוש ייחוס לפתיחה (Python, ספריית cryptography)
import base64, hashlib, json, struct, sys
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

raw = open(sys.argv[1], "rb").read()
assert raw[:8] == b"SEALBY-W" and raw[8] == 1 and raw[9] == 1
assert raw[14:16] == b"\x00\x00"
header, salt = raw[:16], raw[10:14]
frames, off = [], 16
while off < len(raw):
    (n,) = struct.unpack(">I", raw[off:off + 4]); off += 4
    frames.append(raw[off:off + n]); off += n
assert off == len(raw)

kb = base64.urlsafe_b64decode(sys.argv[2] + "=")   # 47-char key string
key, check = kb[:32], kb[32:35]
assert hashlib.sha256(key).digest()[:3] == check    # typo check

N = len(frames)
def chunk(i):
    nonce = salt + struct.pack(">Q", i)
    aad = header + struct.pack(">QQ", i, N)
    return AESGCM(key).decrypt(nonce, frames[i], aad)

manifest = json.loads(chunk(0))
data = b"".join(chunk(i) for i in range(1, N))
assert len(data) == manifest["size"]
open(manifest["name"], "wb").write(data)
print("decrypted ->", manifest["name"])

זה לקבצים בדרך. הכספת היא לקבצים במנוחה.

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

להורדה ב-App Store

iPhone ו-iPad · iOS 17+ · חינם