קריפטוגרפיה
סיסמאות ו-hashing עם Argon2id
למה hashes מהירים נכשלים בסיסמאות, מה salt ו-work factor עושים, ואיך מחשבים ומאמתים Argon2id ב-Python עם argon2-cffi.
הפרדה בין קידוד, הצפנה וגיבוב במעבדות עם Base64, SHA-256 ו-OpenSSL. למה Base64 אינו הגנה ולמה hash הוא חד־כיווני.
קידוד, הצפנה וגיבוב הופכים כולם נתונים למשהו אחר, ולכן אנשים מערבבים ביניהם. המטרות שלהם שונות. קידוד משנה את הדרך שבה הנתונים כתובים, וכל אחד יכול להפוך אותו. הצפנה מסתירה נתונים ממי שאין לו מפתח, ועם המפתח אפשר להפוך אותה. גיבוב מפיק טביעת אצבע קצרה שאי אפשר להחזיר ממנה את הקלט המקורי. אם לזכור משפט אחד: Base64 אינו הצפנה, ו-hash לא מסתיר שום דבר לגבי קלטים קצרים.
המאמר מפריד בין שלושת הרעיונות ואז מבקש להריץ כל אחד מהם בטרמינל על אותו טקסט. את ההשוואה בין התוצאות עושים לבד.
צריך טרמינל עם base64, sha256sum וגם openssl. ב-Windows 11 הפקודות שלהלן רצות ב-Git Bash, שמגיע עם Git for Windows. יש גם גרסת PowerShell לשלב הגיבוב. לא נדרש ידע קודם בקריפטוגרפיה, אבל קריאה של המאמר על צופן קיסר קודם עוזרת להבין את הרעיון של מפתח.
כל פקודה במעבדה בטוחה להרצה על המחשב שלך. צריך להשתמש בקובץ בדיקה, לא בסוד אמיתי.
שלוש הפעולות עונות על שלוש שאלות שונות.
| פעולה | השאלה שהיא עונה עליה | צריך מפתח? | אפשר להפוך? |
|---|---|---|---|
| קידוד | איך לייצג את הבתים האלה כטקסט או בפורמט שמערכת אחרת מצפה לו? | לא | כן, כל אחד |
| הצפנה | מי יכול לקרוא את הנתונים האלה? | כן | כן, מי שמחזיק במפתח הנכון |
| גיבוב | האם הנתונים האלה השתנו, או האם שני ערכים תואמים? | לא | לא, בכוונה |
כדאי לזכור את הטבלה הזו במהלך המעבדה. שאר המאמר מסביר כל שורה.
קידוד כותב נתונים מחדש בצורה אחרת כדי שמערכת תוכל להעביר אותם. הדוגמה הקלאסית היא Base64. הוא לוקח בתים וכותב אותם ב-64 תווים הניתנים להדפסה: A עד Z, a עד z, 0 עד 9, + וגם /, והתו = משמש למילוי. הפורמט מוגדר ב-RFC 4648. קבצים מצורפים לאימייל, data URLs והרבה payloads של API משתמשים בו כי התעבורה מצפה לטקסט.
ב-Base64 אין סוד. המיפוי ציבורי וקבוע. כל כלי שמכיר את הכלל יכול להחזיר את הפלט לקלט. זו המטרה של קידוד: תאימות בין מערכות, לא סודיות.
הצפנה לוקחת נתונים קריאים, שנקראים plaintext, והופכת אותם בעזרת אלגוריתם ומפתח ל-ciphertext. מי שאין לו מפתח לא אמור ללמוד מה-ciphertext שום דבר שימושי. מי שיש לו מפתח יכול לפענח אותו.
יש שתי משפחות. הצפנה סימטרית משתמשת באותו מפתח סודי להצפנה ולפענוח. AES היא הבחירה הנפוצה. הצפנה אסימטרית משתמשת במפתח ציבורי ובמפתח פרטי. המאמר משתמש בהצפנה סימטרית, כי גם פקודת OpenSSL במעבדה עושה זאת.
הצפנה טובה תלויה ביותר מהאלגוריתם. צריך לבחור מצב הפעלה בטוח, ליצור מפתחות ממקור אקראיות טוב ולהגן על המפתח. פקודת OpenSSL במעבדה משתמשת בסיסמה וב-PBKDF2 כדי לגזור את המפתח. לפי טקסט העזרה של OpenSSL, PBKDF2 משתמש ב-10,000 איטרציות כברירת מחדל, אלא אם משנים את זה עם -iter.
פונקציית hash קריפטוגרפית מקבלת קלט בכל אורך ומחזירה פלט באורך קבוע. SHA-256 מוגדר ב-FIPS 180-4, והפלט שלו הוא תמיד 256 ביטים, שנכתבים כ-64 תווים הקסדצימליים. למתחילים חשובות שתי תכונות:
גיבוב הוא חד־כיווני. שום דבר בפלט לא מגלה את הטקסט המקורי. אפשר לבדוק אם קלט מועמד מפיק hash ידוע. אי אפשר לשחזר את הקלט מה-hash לבדו, אלא בניחוש ובדיקה.
ה-hash הוא טביעת אצבע, לא מנעול. כל אחד יכול לחשב SHA-256 של קובץ ציבורי. hash מוכיח ששני עותקים תואמים. הוא לא מסתיר את הקובץ.
רוב טעויות האבטחה האמיתיות בתחום הזה נובעות משימוש בכלי הלא נכון.
הבנת ההבדל בשלב מוקדם חוסכת את המלכודות האלה.
לפתוח Git Bash בתיקייה ריקה. לקודד ביטוי קצר ואז לבטל את הקידוד של התוצאה.
echo -n 'hello lab' | base64הפלט שקיבלתי על המחשב הזה עם OpenSSL 3.2.4 ו-Git Bash:
aGVsbG8gbGFiעכשיו מבטלים את הקידוד של הפלט:
echo -n 'aGVsbG8gbGFi' | base64 -dפלט:
hello labשני דברים לשים אליהם לב. קודם כול, לא היה מעורב שום מפתח. שנית, echo -n חשוב כאן. בלי -n, הפקודה echo מוסיפה ירידת שורה ופלט ה-Base64 משתנה. כדי להשוות, אפשר להריץ echo 'hello lab' | base64: אני קיבלתי aGVsbG8gbGFiCg==. Base64 הופך כאן 9 בתים ל-12 תווים, ולכן הפלט ארוך מהקלט.
השאלה לבדיקה: האם מישהו בלי שום סוד יכול להפוך את זה? כן. זה המבחן שמפריד בין קידוד להצפנה.
להעביר את אותו ביטוי דרך SHA-256:
echo -n 'hello lab' | sha256sumפלט:
8fc6baba79a0d60b37dee612ccc1ec096ef52dba8cb0a24a6451e8bfb0e4d761 *-עכשיו להוסיף סימן קריאה אחד ולהריץ שוב:
echo -n 'hello lab!' | sha256sumפלט:
36902fb7fdea33458ad09dfeb52118dc47f99a8ca2b9001080366d55511de915 *-לשני ה-hashes אין דפוס משותף שאפשר לראות. תו אחד שינה את כל הפלט. שניהם באורך 64 תווים הקסדצימליים, כלומר 256 ביטים.
התו - בסוף אומר שהקלט הגיע מהקלט הסטנדרטי ולא מקובץ. התו * לפניו הוא סימון מצב בינארי שמדפיס sha256sum של Git Bash. מערכות אחרות עשויות להדפיס שני רווחים במקומו, וה-hash זהה. hash של קובץ עובד באותה דרך.
ליצור קובץ קטן ולגבב אותו:
echo 'secret note' > note.txt
sha256sum note.txtעל המחשב שלי, עם סיומות שורה של Unix בקובץ, הפלט היה:
b5f891a5e064fec661025d0b0ec2de157487a6c2e5cc46dc0971fee56f86c645 *note.txtלשנות את הקובץ, אפילו תו אחד, ולגבב שוב. הערך יהיה שונה. כך עובדות בדיקות שלמות. הבדיקה אומרת שהקובץ השתנה. היא לא אומרת מה השתנה, והיא לא מונעת מתוקף לשנות גם את הקובץ וגם את ה-hash המפורסם.
אם PowerShell נוח יותר, יש ב-Windows פקודה מובנית:
Get-FileHash note.txt -Algorithm SHA256בפלט צריך לחפש את השורה Hash, שמציגה 64 תווים הקסדצימליים. ה-hash תלוי בבתים המדויקים בקובץ. הפקודה Set-Content של PowerShell כותבת סיומות שורה של Windows (CRLF), ששונות מסיומת LF בדוגמת ה-Bash שלמעלה. על המחשב שלי, קובץ שנכתב כך נתן את ה-hash הזה:
406A5646AE75CA75D032F306ABD33085DC9F656E3109BB962AEAB335EEC9ED85אותם בתים שגובבו עם sha256sum ב-Git Bash נתנו את אותו ערך באותיות קטנות. אם ה-hash שלך שונה מדוגמת ה-Bash אבל תואם לתוצאת PowerShell עבור הקובץ שלך, הסיבה היא סיומות השורה.
ליצור פתק ולהצפין אותו עם AES-256-CBC. OpenSSL יבקש סיסמה. כדאי לבחור סיסמה שקל לזכור ושמשמשת רק למעבדה הזו.
echo 'secret note' > note.txt
openssl enc -aes-256-cbc -pbkdf2 -salt -in note.txt -out note.encOpenSSL מבקש את הסיסמה, והקובץ המוצפן נכתב אל note.enc. האפשרות -salt מוסיפה salt אקראי, כך שהצפנה של אותו קובץ פעמיים נותנת פלט שונה. קובץ מוצפן שנוצר עם -salt מתחיל בטקסט ASCII הזה: Salted__. בדקתי את זה על המחשב שלי. שאר הקובץ בינארי ולא קריא.
עכשיו מפענחים אותו:
openssl enc -d -aes-256-cbc -pbkdf2 -in note.encלהזין את אותה סיסמה. פלט:
secret noteהפענוח עובד רק עם הסיסמה הנכונה. בדקתי עם סיסמה שגויה. OpenSSL הדפיס bad decrypt ויצא עם שגיאה.
בשלב הבא משנים בית אחד בקובץ המוצפן ומנסים לפענח אותו. הפכתי ביט אחד בתוך בלוק ה-ciphertext של עותק בשם tampered.enc. הפענוח נכשל עם bad decrypt, והפקודה יצאה עם סטטוס 1.
הכישלון הזה הוא מקרה מזל של ה-padding ב-CBC. שינויים אחרים יכולים להפיק פלט זבל בלי שגיאה. הלקח הוא שהצפנה רגילה לא נותנת אות אמין לכך שה-ciphertext שונה. מערכות שצריכות גם סודיות וגם שלמות משתמשות בהצפנה מאומתת, כמו AES-GCM, או מוסיפות קוד אימות הודעה. זה נושא מתקדם, אבל הוא מסביר למה קובץ מוצפן אינו אוטומטית קובץ מהימן.
להעביר את אותו טקסט דרך כל פעולה ולהשוות:
| פעולה | פקודה | פלט על hello lab (נצפה) |
|---|---|---|
| קידוד | echo -n 'hello lab' | base64 |
aGVsbG8gbGFi |
| גיבוב | echo -n 'hello lab' | sha256sum |
8fc6baba...e4d761 |
| הצפנה | openssl enc ... -in note.txt |
נתונים בינאריים, צריך את הסיסמה כדי לקרוא |
מה כל פלט מאפשר:
hello lab.hello lab, אבל לא לשחזר את הקלט מה-hash לבדו.secret note רק עם הסיסמה.לקרוא ל-Base64 "הצפנה" או "obfuscation" כשמדובר בסודות. Base64 לא מסתיר כלום. כל מי שרואה מחרוזת Base64 יכול לבטל את הקידוד שלה. סודות שומרים במנהל סודות או באחסון מוצפן, לא בצורה מקודדת.
להשתמש ב-hash כמנעול. hash הוא חד־כיווני, אבל קלטים קצרים או נפוצים אפשר לנחש. אם מגבבים את hello lab, תוקף עם רשימת ביטויים נפוצים משחזר אותו בלי לשבור את SHA-256. לכן גיבוב סיסמאות צריך salts ואלגוריתמים איטיים.
להשתמש ב-SHA-256 רגיל לסיסמאות. SHA-256 מהיר בתכנון שלו, וזה מתאים ל-checksums של קבצים ולא מתאים לאחסון סיסמאות. צריך להשתמש בפונקציית גיבוב סיסמאות שנבנתה לזה.
לסמוך על hash מאותו מקום כמו הקובץ. checksum באותו דף שנפרץ לא מוכיח כלום. צריך לקבל את ה-hash הצפוי בערוץ נפרד ומהימן, או לאמת חתימה דיגיטלית.
לשכוח שהצפנה דורשת ניהול מפתחות. המעבדה משתמשת בסיסמה שמקלידים כשהפקודה מבקשת אותה. מערכות אמיתיות חייבות לאחסן, להחליף ולהגן על מפתחות. אובדן המפתח הוא אובדן הנתונים.
לכתוב קריפטוגרפיה משלך. צריך להשתמש בספריות ובפרוטוקולים מבוססים. פקודות המעבדה נועדו ללמידה, והן לא תבנית למערכות production.
לחזור על שלוש המעבדות עם ביטוי אחר, ואז עם קובץ של כמה קילובייטים. פלט ה-Base64 גדל בערך בשליש, ה-hash נשאר באורך 64 תווים, והקובץ המוצפן גדל רק בכותרת וב-padding.
בהמשך כדאי לקרוא על גיבוב סיסמאות. הבעיה שם היא עלות הניחוש, לא הגיבוב כשלעצמו. המאמר הבא בסדרה, איך גיבוב סיסמאות עובד עם Argon2id, מסביר salts, work factors ולמה חשוב ש-hashes יהיו איטיים. לרענון של צד הצפנים, אפשר לקרוא קודם את צופן קיסר ב-Python. הוא מציג מפתח ואלגוריתם בצורה שאפשר לבדוק.
בלימוד של פרוטוקולים אמיתיים כדאי לחפש שלושה דברים בכל תכנון: מה הוא מקודד, מה הוא מצפין ומה הוא מגבב. רוב הבלבול נעלם ברגע ששואלים איזה משלושת התפקידים כל חלק ממלא.
קריפטוגרפיה
למה hashes מהירים נכשלים בסיסמאות, מה salt ו-work factor עושים, ואיך מחשבים ומאמתים Argon2id ב-Python עם argon2-cffi.
קריפטוגרפיה
מפתחות ציבוריים ופרטיים מאפס: מה חתימה מוכיחה, למה המפתח הפרטי נשאר סודי, ומעבדת Ed25519 שבה קובץ ששונה נכשל באימות.
קריפטוגרפיה
קריפטוגרפיה מאפס: הצפנה, מפתחות, hashes, חתימות, ולמה צופן קיסר מלמד רעיונות אבל לא מגן על כלום.