דילוג לתוכן
New

DeepSeek V4.1 Flash free for 7 days: 10M tokens a day after a top-up of $10 or more. Until Nov 30.

How it works

מפתחות ציבוריים ופרטיים: חתימות שאפשר לאמת

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

מתחילים8 דקות קריאהמאת NotHoly

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

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

מהו זוג מפתחות

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

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

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

הצפנה וחתימה עושות עבודות הפוכות

אנשים מבלבלים ביניהן כל הזמן, אז כדאי להפריד ביניהן.

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

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

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

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

למה אסור למפתח הפרטי לצאת מידי בעליו

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

מכאן נובעים ארבעה כללים מעשיים:

  • יוצרים את המפתח הפרטי על המחשב שישתמש בו, או על טוקן חומרה שנבנה לזה. לא מורידים מפתח פרטי מאתר ולא מקבלים אותו בצ׳אט.
  • מגבילים הרשאות קבצים. מגינים על המפתח עם passphrase כשהכלי תומך בזה, או שומרים אותו באחסון מגובה חומרה.
  • המפתח נשאר מחוץ ל-Git, לכרטיסים, לצילומי מסך ולפניות תמיכה. סורקי סודות מחפשים כותרות PEM. ברגע שמפתח מגיע להיסטוריה של repository, מתייחסים אליו כאל חשוף, גם אחרי מחיקת הקובץ.
  • כשיש חשד לחשיפה, מוציאים את המפתח משימוש. יוצרים זוג חדש, מפרסמים את המפתח הציבורי החדש בערוץ שהמאמתים סומכים עליו ומבטלים את הישן. מחיקת הקובץ המקומי לא מסירה עותקים שכבר יצאו מהמחשב שלך.

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

איך חתימה ואימות עובדים, שלב אחר שלב

הרצף של גרסה חתומה נראה כך:

  1. המפרסם יוצר זוג מפתחות פעם אחת ושומר את המפתח הפרטי בסוד.
  2. המפרסם משתף את המפתח הציבורי בערוץ שהמאמת סומך עליו, כמו דף שהמאמת כבר סומך עליו, טביעת אצבע שפורסמה במקום שני, או מפתח שאומת קודם.
  3. עבור כל גרסה, המפרסם חותם על הבתים המדויקים של הקובץ עם המפתח הפרטי. חתימת Ed25519 היא 64 בתים.
  4. המפרסם מפיץ את הקובץ ואת החתימה שלו יחד.
  5. המאמת מריץ את הבדיקה עם הבתים של הקובץ, החתימה והמפתח הציבורי המהימן. התשובה היא תקף או לא תקף.

שני פרטים חשובים יותר ממה שנדמה. ראשית, חתימה מכסה בתים מדויקים. רווח אחד נוסף, סיומת שורה אחרת או ארכיון שנארז מחדש הם הודעה אחרת, והאימות נכשל. שנית, חתימת Ed25519 לפי RFC 8032 היא דטרמיניסטית. אותו מפתח ואותה הודעה נותנים תמיד אותה חתימה, כך שהחתימה לא צריכה מספר אקראי חדש בכל פעם. זה מסיר מקור נפוץ אחד לכשל. FIPS 186-5 מונה את EdDSA יחד עם ECDSA ו-RSA כאלגוריתמי חתימה דיגיטלית מאושרים.

מעבדה מעשית: לחתום על קובץ ולראות את האימות נכשל

צריך OpenSSL 3.x. הפלט במעבדה הזאת מגיע מ-Windows 11 עם Git Bash ו-OpenSSL 3.2.4. קודם בודקים את הגרסה עם openssl version. גרסאות ישנות יותר עלולות לא לקבל את אותן אפשרויות.

משתמשים ב-Git Bash, ב-shell של Linux או ב-Terminal של macOS. Windows PowerShell 5.1 כותב UTF-16 בהפניה עם >, וזה משנה את הבתים שעליהם חותמים. את המעבדה מריצים בתיקייה ריקה על המחשב שלך. היא לא פונה לשום שרת.

יצירת זוג המפתחות

bash
openssl genpkey -algorithm ed25519 -out priv.pem
openssl pkey -in priv.pem -pubout -out pub.pem

שתי הפקודות לא מדפיסות כלום כשהן מצליחות. בודקים את התוצאות:

bash
ls -l priv.pem pub.pem

אצלנו הקובץ priv.pem היה 122 בתים והקובץ pub.pem היה 116 בתים. בקובץ pub.pem אפשר לראות את הבלוק -----BEGIN PUBLIC KEY-----. לא מדפיסים ולא מדביקים את priv.pem. במעבדה הזאת הוא החצי הסודי של הזהות, וההרגל שלא להציג אותו לעולם יידרש גם במפתחות SSH.

חתימה על הקובץ

bash
echo 'release v1.0' > msg.txt
openssl pkeyutl -sign -inkey priv.pem -rawin -in msg.txt -out msg.sig
ls -l msg.sig

האפשרות -rawin אומרת ל-OpenSSL לחתום ישירות על תוכן הקובץ. Ed25519 מחשב hash על הקלט בתוך האלגוריתם, ולכן לא מחשבים hash לקובץ קודם. פקודת החתימה לא מדפיסה כלום. msg.sig שלנו היה 64 בתים.

אימות עם המפתח הציבורי

bash
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in msg.txt -sigfile msg.sig

פלט:

text
Signature Verified Successfully

הדגל -pubin אומר שקובץ המפתח מחזיק רק את החצי הציבורי. מאמת לא צריך שום דבר אחר, כך שהבדיקה יכולה לרוץ על מחשב אחר שמעולם לא ראה את priv.pem.

שינוי שורה אחת ואימות חוזר

bash
echo 'release v1.1' > msg.txt
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in msg.txt -sigfile msg.sig

פלט:

text
Signature Verification Failure

הפקודה יוצאת עם סטטוס 1. החתימה בקובץ msg.sig נוצרה על השורה release v1.0, ולכן היא לא יכולה לערוב לשורה release v1.1. OpenSSL לא אומר מה השתנה. הוא מדווח רק שהבתים והחתימה לא מסכימים. מאמת צריך להתייחס לתוצאה הזאת כעצירה מוחלטת, ולא כאזהרה שמדלגים עליה בלחיצה.

בדיקת הקצוות

הבדיקות האלה מראות בדיוק מה החתימה מכסה. הרצנו כל אחת מהן על אותו מחשב ובאותה תיקייה.

חתימה כפולה על אותו קובץ נותנת את אותם בתים:

bash
echo 'release v1.0' > msg.txt
openssl pkeyutl -sign -inkey priv.pem -rawin -in msg.txt -out a.sig
openssl pkeyutl -sign -inkey priv.pem -rawin -in msg.txt -out b.sig
cmp a.sig b.sig && echo "identical"

פלט:

text
identical

קובץ בלי תו השורה החדשה בסוף נכשל. echo מוסיף תו שורה חדשה, ולכן הקובץ הזה קצר בבית אחד:

bash
printf 'release v1.0' > nonl.txt
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in nonl.txt -sigfile a.sig

פלט:

text
Signature Verification Failure

msg.txt הוא 13 בתים והקובץ nonl.txt הוא 12 בתים. החתימה נוצרה על הגרסה של 13 הבתים, ולכן הקובץ הקצר יותר נכשל.

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

bash
openssl genpkey -algorithm ed25519 -out other.pem
openssl pkey -in other.pem -pubout -out other.pub
openssl pkeyutl -verify -pubin -inkey other.pub -rawin -in msg.txt -sigfile a.sig

פלט:

text
Signature Verification Failure

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

טעויות נפוצות

  • התייחסות לחתימה כאל הצפנה. קובץ חתום נשאר קריא לכל אחד. כשצריך סודיות, צריך הצפנה, וזה מבנה נפרד.
  • אמון בכל מפתח ציבורי שמוצאים. חתימה תקפה מוכיחה שהמחזיק במפתח חתם על הבתים. היא לא מוכיחה מי המחזיק במפתח. משווים טביעות אצבע בערוץ שני.
  • אימות של בתים שונים מאלה שנחתמו. צינור שמקודד מחדש, דוחס מחדש או משנה סיומות שורה שובר את האימות. חותמים על הארטיפקט הסופי ומאמתים את אותו ארטיפקט.
  • חתימה ב-shell שמשכתב בתים. הפניית פלט ב-Windows PowerShell 5.1 היא דוגמה אחת. עורכים ששומרים בקידוד או בסיומת שורה אחרים הם דוגמה נוספת.
  • שימוש חוזר במפתח אחד לכל דבר. מפתחות נפרדים לחתימת גרסאות, להתחברות ב-SSH ולהצפנה מגבילים את הנזק כשמפתח אחד דולף.
  • הנחה שמפתח שנמחק נעלם. עותקים בגיבויים, בתיקיות סנכרון, בהיסטוריית ה-shell או בלוגים של צ׳אט נשארים אחרי מחיקה מקומית.
  • בחירת פרימיטיבים לפי שם. "RSA" או "ECC" לא אומרים באיזה padding, עקום או מצב משתמשים. קוראים את הפרמטרים בתקן, ולא רק את התווית.

איך ממשיכים

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

להעמקה, RFC 8032 מראה איך Ed25519 בונה את החתימות שלו ולמה הוא דטרמיניסטי. FIPS 186-5 מראה איפה EdDSA נמצא בין אלגוריתמי החתימה המאושרים. דפי ה-man של genpkey ושל pkeyutl ב-OpenSSL מראים את שאר האפשרויות, כמו בחירת digest באלגוריתמים שדורשים אותה. אחר כך כדאי לאמת גרסה של פרויקט שכבר משתמשים בו, עם המפתח שהפרויקט מפרסם באתר שלו. להריץ רק קבצים שהורדו מהמקור הרשמי, ורק אחרי שנבדקו.

מקורות

התקנים ודפי ה-man שלמעלה הם המקורות הראשוניים של המאמר:

  • RFC 8032, Edwards-Curve Digital Signature Algorithm (EdDSA)
  • FIPS 186-5, Digital Signature Standard (DSS)
  • OpenSSL documentation, openssl-genpkey
  • OpenSSL documentation, openssl-pkeyutl

מקורות

  1. RFC 8032: Edwards-Curve Digital Signature Algorithm (EdDSA)
  2. FIPS 186-5: Digital Signature Standard (DSS)
  3. OpenSSL documentation: openssl-genpkey
  4. OpenSSL documentation: openssl-pkeyutl

עוד בנושא קריפטוגרפיה

קריפטוגרפיה

סיסמאות ו-hashing עם Argon2id

למה hashes מהירים נכשלים בסיסמאות, מה salt ו-work factor עושים, ואיך מחשבים ומאמתים Argon2id ב-Python עם argon2-cffi.

ביניים8 דקות קריאה