מה זו חתימה דיגיטלית?

כיצד חתימה קריפטוגרפית עובדת - הסבר פשוט וקל

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

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

אז מה המטרה פה?

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

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

איך חותמים?

ההליך הטכני של החתימה הולך ככה:

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

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

הדגמה! בפייתון

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

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

uv init
uv venv
uv add cryptography

יאי! נכנס ל-main.py (שמי שיצר לנו אותו הוא uv init)

אני משתמש במפתחות הקריפטוגרפיים הרגילים שלי שנמצאים בתיקית ssh./~ שלי – מי שלא יודע איך ליצור מפתחות – אז צריך אותם כדי להתחבר לגיטהאב ולשרתים אחרים (גם לרספברי פיי ובפוסט הזה אפשר לקרוא עליהם.

החלטה נוספת שצריך זה האם לשמור את החתימה בפורמט בינארי או בפורמט טקסטואלי (base64). החתימה היא סדרת בייטים גולמיים – כלומר מידע בינארי וברירת המחדל היא לשמור את החתימה כקובץ בינארי והבייטים נשמרים ללא עיבוד נוסף, מה שמבטיח שלמות ודיוק בעת אימות החתימה. א-ב-ל אם יש צורך להעביר את החתימה באמצעי טקסטואלי (למשל דרך אימייל, JSON, או קובץ טקסט, קעקוע על גב של מישהו), אז אפשר להמיר את החתימה לייצוג טקסטואלי קריא שזה base64. בד״כ אני משתמש בבינארי – אבל יש אפשרות אחרת.

א��רי שקיבלתי את ההחלטות האלו, אני אגש אל ה-LLM הקרוב (בד״כ קופיילוט אבל כל אחד עם מה שבא לו) ואבקש ממנו. חשוב לפרט לו את המפתח ואת הפורמט שרוצים. משהו בסגנון:

כתוב קוד Python שיוצר חתימה דיגיטלית מאובטחת לקובץ PDF באמצעות מפתח פרטי קיים (למשל ~/.ssh/id_ed25519). הקוד צריך לטעון את המפתח הפרטי מהקובץ במערכת, להשתמש באלגוריתם Ed25519, ולחתום על תוכן הקובץ. לאחר החתימה, יש לשמור את החתימה בקובץ בינארי (signature.bin).

תקבלו משהו כזה:

import os
from cryptography.hazmat.primitives import serialization

# 1️⃣ Load the private key from ~/.ssh/id_ed25519
with open(f"{os.path.expanduser('~')}/.ssh/id_ed25519", "rb") as key_file:
    private_key = serialization.load_ssh_private_key(key_file.read(), password=None)

# 2️⃣ Read the PDF data
with open("original-pdf-file.pdf", "rb") as f:
    pdf_data = f.read()

# 3️⃣ Sign the data
signature = private_key.sign(pdf_data)

# 4️⃣ Save the signature to a file
with open("signature.bin", "wb") as sig_file:
    sig_file.write(signature)
print("???? Signature saved to 'signature.bin'.")

אם תשמרו את הקובץ ב-sign.py ותריצו אותו עם uv run sign.py, אכן תיווצר חתימה!

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

וידוא החתימה

הקוד של החתימה פשוט למדי, אבל אם אני אשתמש במפתח הפומבי ״הרגיל״ שלי, אני אתקל בבעיה כי ספרית הקריפטוגרפיה של פייתון דורשת פורמט מאד מסוים והיא לא מעט פעמים מציקה גם במפתחות תקינים לחלוטין. יש לנו שתי אפשרויות – הראשונה להמיר את המפתח הציבורי שלי לפורמט pem והשניה היא להשתמש בספרית pynacl כדי שתבצע את ההמרה on the fly. במה לבחור? תלוי בכם! אני כן אשתמש ב-pynacl:

import os
import base64
from nacl.signing import VerifyKey
from nacl.exceptions import BadSignatureError

# 1️⃣ Read the PDF data
with open("original-pdf-file.pdf", "rb") as f:
    pdf_data = f.read()

# 2️⃣ Read and decode the SSH public key
with open(os.path.expanduser("~/.ssh/id_ed25519.pub"), "r") as f:
    pubkey_line = f.read().strip()

# Extract base64-encoded key blob
key_base64 = pubkey_line.split()[1]
key_blob = base64.b64decode(key_base64)

# Extract the raw 32-byte Ed25519 key from the SSH key blob
offset = 4 + len("ssh-ed25519") 
pubkey_length = int.from_bytes(key_blob[offset:offset+4], "big")
raw_key = key_blob[offset+4:offset+4+pubkey_length]

# 3️⃣ Create VerifyKey
verify_key = VerifyKey(raw_key)

# 4️⃣ Read the signature
with open("signature.bin", "rb") as f:
    signature = f.read()

# 5️⃣ Verify the signature
try:
    verify_key.verify(pdf_data, signature)
    print("✅ Signature is valid!")
except BadSignatureError:
    print("❌ Signature verification failed!")

אם תריצו את זה עם uv run verify-sign.py, תגלו ש-Yay! החתימה תקינה לגמרי! כלומר גם המקור תקין (לפי המפתח הציבורי שלי) וגם המידע עבר ולידציה

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

(echo -ne '\x00\x01'; cat original-pdf-file.pdf) > temp && mv temp original-pdf-file.pdf

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

סיכום

חתימה אלקטרונית מוודאת גם Authenticity – כלומר שמי שאומר שיצר את המסמך באמת יצר אותו (הוא לא יוכל להכחיש אחר כך, כי זה המפתח הפרטי שלו שביצע את החתימה!) וגם Integrity – שאף אחד לא נגע ולו בביט אחד של המסמך שנחתם.

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

פוסטים נוספים שכדאי לקרוא

בינה מלאכותית

זיהוי אנומליות עם tflite

איך משתמשים במידע מחיישנים של IoT ובונים איתו מודל tflite. דרך ההבנה הזו נלמד על למידת מכונה.

יסודות בתכנות

מבוא לאבטחת מידע: גוגל דורקינג

מאמר מבוא המספר בקצרה ובלשון קלה על גוגל דורקינג – טכניקה לביצוע האקינג גם ללא ידע טכני כלל.

רשת האינטרנט

איך בונים custom GPT משלכם?

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

גלילה לראש העמוד