צפה בסרטון השיעור: אבטחת סוכני AI עם קבלות קריפטוגרפיות
(סרטון השיעור והתמונה המקדימה יתווספו על ידי צוות התוכן של מיקרוסופט לאחר המיזוג, בהתאמה לתבנית השיעור 14 / 15.)
בשיעור זה נסקור:
בסיום שיעור זה, תדעו כיצד:
דמיין שהפעלת סוכן AI עבור Contoso Travel. הסוכן קורא בקשות לקוחות, קורא ל-API טיסות כדי לחפש אפשרויות ומבצע הזמנות בשם הלקוח. ברבעון האחרון, הסוכן טפל ב-50,000 הזמנות.
היום מגיע מבקר. הוא שואל שאלה פשוטה: “הראה לי מה הסוכן שלך עשה.”
אתה מוסר את קובצי הלוג שלך. המבקר מסתכל עליהם ושואל שאלה קשה יותר: “איך אני יודע שהלוגים האלה לא נערכו?”
זו הבעיה של מסלול הביקורת. רוב הפעלות הסוכנים כיום מסתמכות על:
אף אחד מהם אינו יכול לענות על שאלת המבקר מבלי לדרוש ממנו לסמוך על מישהו (אתה, ספק הענן שלך, ספק מסד הנתונים שלך). לצרכים פנימיים, אימון זה מקובל בדרך כלל. לעומס עבודה מפוקח (פיננסים, בריאות, כל דבר בכפוף לחוק AI של האיחוד האירופי) זה אינו מקובל.
קבלות קריפטוגרפיות פותרות זאת בכך שהן מאפשרות אימות עצמאי לכל פעולה של הסוכן. המבקר אינו צריך לסמוך עליך. הוא צריך רק את המפתח הציבורי שלך ואת הקבלה עצמה.
קבלה היא אובייקט JSON המתעד מה הסוכן עשה, וחתום בחתימה דיגיטלית.
flowchart LR
A[הסוכן מפעיל כלי] --> B[בנה מטען קבלה]
B --> C[תקנון JSON RFC 8785]
C --> D[גיבוב SHA-256]
D --> E[חתימת Ed25519]
E --> F[קבלה עם חתימה]
F --> G[המבקר מאמת לא מקוון]
G --> H{האם החתימה תקפה?}
H -- yes --> I[הוכחה עמידה לזיוף]
H -- no --> J[הקבלה נדחתה]
קבלה מינימלית נראית כך:
{
"type": "agent.tool_call.v1",
"agent_id": "contoso-travel-bot",
"tool_name": "lookup_flights",
"tool_args_hash": "sha256:a3f9c1...",
"result_hash": "sha256:7b2e1d...",
"policy_id": "contoso-travel-policy-v3",
"timestamp": "2026-04-25T14:30:00Z",
"sequence": 47,
"previous_receipt_hash": "sha256:9d4e6a...",
"signature": {
"alg": "EdDSA",
"sig": "c5af83...",
"public_key": "8f3b2c..."
}
}
שלוש תכונות מבצעות את העבודה:
החתימה. הקבלה חתומה באמצעות שער הסוכן על ידי מפתח פרטי Ed25519. כל מי שיש לו את המפתח הציבורי המתאים יכול לאמת את החתימה במצב לא מקוון. זיוף בכל שדה יבטל את החתימה.
קידוד קנוני. לפני החתימה, הקבלה מומרת לפי סכמת קנוניזציה של JSON (JCS, RFC 8785). זה מבטיח ששתי יישומים המייצרים את אותה קבלה לוגית יפיקו בדיוק אותו רצף בתים. ללא קנוניזציה, סיריאלייזרים שונים של JSON היו מייצרים חתימות שונות על אותו תוכן.
שרשור גיבוב. שדה previous_receipt_hash מקשר כל קבלה לזו שלפניה. הסרה או שינוי סדר של קבלה תשבור כל קבלה שבאה אחריה. זיוף מתגלה ברמת השרשרת אפילו אם חתימות בודדות עוברות.
ביחד, תכונות אלו מספקות שלוש הבטחות:
אינך צריך ספרייה מיוחדת כדי להפיק קבלה. היסודות הקריפטוגרפיים זמינים ברבים והלוגיקה היא כמה עשרות שורות פייתון.
התרגילים המעשיים ב-code_samples/18-signed-receipts.ipynb מראים את כל התהליך. הגרסה המסכמת:
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize # JSON הקנוני לפי RFC 8785
def b64url_nopad(data: bytes) -> str:
return base64.urlsafe_b64encode(data).decode("ascii").rstrip("=")
def sha256_canonical(obj) -> str:
"""SHA-256 of a Python object's JCS-canonical JSON form."""
return f"sha256:{hashlib.sha256(canonicalize(obj)).hexdigest()}"
# צור או טען מפתח חתימה (בייצור, אחסן בוולט מפתחות)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
# בנה את מטען הקבלה (עדיין ללא חתימה)
tool_args = {"origin": "SYD", "destination": "LAX"}
tool_result = [{"flight": "QF11", "price": 1850, "stops": 0}]
payload = {
"type": "agent.tool_call.v1",
"agent_id": "contoso-travel-bot",
"tool_name": "lookup_flights",
"tool_args_hash": sha256_canonical(tool_args),
"result_hash": sha256_canonical(tool_result),
"policy_id": "contoso-travel-policy-v3",
"timestamp": "2026-04-25T14:30:00Z",
"sequence": 0,
"previous_receipt_hash": None,
}
# הפוך לקנוני, חשב גיבוב, חתום.
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
signature_bytes = signing_key.sign(message_hash).signature
# צרף אובייקט חתימה מובנה.
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
זו כל צינור החתימה. התרגילים במחברת מתארים כל שלב.
אימות הוא הפעולה ההפוכה:
import base64
import hashlib
from nacl import signing
from nacl.exceptions import BadSignatureError
from jcs import canonicalize
def b64url_decode(s: str) -> bytes:
padding = "=" * ((4 - len(s) % 4) % 4)
return base64.urlsafe_b64decode(s + padding)
def verify_receipt(receipt: dict) -> bool:
# החתימה היא אובייקט מובנה: {"alg", "sig", "public_key"}.
sig_obj = receipt.get("signature")
if not sig_obj or sig_obj.get("alg") != "EdDSA":
return False
# שוחזר את המטען שנסמן בפועל (הכל חוץ מהחתימה).
payload = {k: v for k, v in receipt.items() if k != "signature"}
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
try:
verify_key = signing.VerifyKey(b64url_decode(sig_obj["public_key"]))
verify_key.verify(message_hash, b64url_decode(sig_obj["sig"]))
return True
except BadSignatureError:
return False
פונקציה זו מקבלת קבלה ומחזירה True אם החתימה תקפה, אחרת False. אין קריאות רשת, אין תלות בשירות, אין צורך באמון לצד שלישי.
כדי לראות זיהוי זיוף בפעולה, המחברת עוברת על:
tool_args_hash.זו ההדגמה הפרקטית שקבלות הן עדות לזיוף: כל שינוי, אפילו קטן, משבר את החתימה.
קבלה חתומה יחידה מגנה על פעולה אחת. שרשרת קבלות מגנה על רצף פעולות.
flowchart LR
R0[קבלה 0<br/>גנזיס] --> R1[קבלה 1]
R1 --> R2[קבלה 2]
R2 --> R3[קבלה 3]
R1 -. previous_receipt_hash .-> R0
R2 -. previous_receipt_hash .-> R1
R3 -. previous_receipt_hash .-> R2
כל קבלה מתעדת את גיבוב הקבלה שלפניה. כדי להסיר את קבלה מספר 2 בשקט, התוקף יצטרך:
previous_receipt_hash של קבלה 3 (שובר את החתימה של קבלה 3), אואם המפתח הפרטי מאוחסן במיכל מפתחות חומרתי ואתה מפרסם את המפתח הציבורי עם כל קבלה, אף אחד מהתקפות אלו אינו אפשרי בלי גילוי.
המחברת עוברת על:
previous_receipt_hash של כל קבלה תואם לגיבוב האמיתי של הקבלה הקודמת.ככה מייצרים מסלול ביקורת שמבקר חיצוני יכול לוודא בלי לסמוך עליך.
זהו החלק החשוב ביותר בשיעור זה. קבלות הן כלי רב עוצמה אך תחום כוחן מוגבל.
קבלות מוכיחות שלושה דברים:
קבלות אינן מוכיחות:
policy_id אכן הוערכה, או שהייתה מאפשרת את הפעולה אם הייתה נבדקת. הקבלה מתעדת רק את הדרישה, לא את האכיפה.גבול זה חשוב משתי סיבות:
טעות נפוצה היא להניח ש”יש לנו קבלות” פירושו “אנחנו מפוקחים.” זה לא כך. קבלות הן הבסיס. הפיקוח הוא המערכת שאתה בונה מעליהן.
קוד הפייתון בשיעור זה מכוון להיות מינימלי כדי שתוכל לקרוא כל שורה ולהבין בדיוק מה קורה. בפרודקשן יש לך שתי אפשרויות:
לבנות ישירות על היסודות הקריפטוגרפיים. 50 השורות שראית כאן מספיקות לרוב השימושים. PyNaCl (Ed25519) וחבילת jcs (JSON קנוני) הן ספריות מתוחזקות ומבוקרות היטב.
להשתמש בספריית קבלות לפרודקשן. מספר פרויקטים בקוד פתוח מיישמים דפוס זה עם תכונות נוספות (סיבוב מפתחות, אימות אצווה, הפצת JWK Set, אינטגרציה עם מנועי מדיניות):
draft-farley-acta-signed-receipts) שנמצא בתהליך תקני.protect-mcp (npm) ו-@veritasacta/verify (npm) מספקות מימוש מבוסס Node לחתימה ואימות קבלות במצב לא מקוון, מתוכנן לעטוף כל שרת MCP עם מסלול ביקורת עמיד לזיופים.הבחירה בין בנייה עצמית לשימוש בספריה משקפת את הבחירה בין כתיבת ספריית JWT משלך לשימוש בקיימת: שתיהן הגיוניות; ספריה חוסכת זמן ומפחיתה שטח ביקורת; גישת הגרידאותר מאלצת הבנה מלאה של כל יסוד. השיעור מלמד את הדרך מההתחלה כדי לתת לך בסיס לשני האופציות.
בדוק את הבנתך לפני המעבר לתרגול המעשי.
1. קבלה חתומה עם מפתח פרטי Ed25519 של הסוכן. למבקר יש רק את המפתח הציבורי. האם המבקר יכול לאמת את הקבלה במצב לא מקוון?
2. תוקף משנה את שדה policy_id בקבלה כדי לטעון שהיא נשלטה על ידי מדיניות מתירנית יותר. החתימה הייתה על המטען המקורי. מה קורה באימות?
3. מדוע הקבלה כוללת tool_args_hash ו-result_hash במקום את הארוגומנטים והתוצאה הגולמיים?
4. שדה previous_receipt_hash מקשר כל קבלה לקודמתה. אם תוקף מוחק שקטה קבלה אמצעית בשרשרת, מה הופך ללא תקף?
5. קבלה עוברת אימות בהצלחה. האם זה מוכיח שהפעולה של הסוכן הייתה נכונה, תקינה או עומדת במדיניות?
פתח את code_samples/18-signed-receipts.ipynb והשלם את כל ארבעת הקטעים:
אתגר מורחב 1: הרחב את סכמת הקבלה עם שדה נוסף לבחירתך (למשל, מזהה בקשה למעקב), עדכן את לוגיקת החתימה הקנונית לכלול אותו, ואשר שהקבלה ממשיכה לעבור סבב אימות. לאחר מכן, שנה את השדה לאחר החתימה ואשר שהאימות נכשל. זה יכריח אותך להבין כיצד כל בית בקידוד הקנוני תורם לחתימה. אתגר מתיחה 2: חשב SHA-256 על שתי הקבלות שלך יחד (שרשר את הבתים הקאנוניים שלהן בסדר דטרמיניסטי) והטמע את העיכול המתקבל כשדה חדש בקבלה השלישית לפני החתימה עליה. אמת שכל שלוש הקבלות עדיין עובדות בצורה מעגלית. הרגע בנית הוכחת הכללה שלב אחד: כל מי שמחזיק בקבלה השלישית יכול להוכיח שהקבלות הראשונות התקיימו במועד שבו נחתמה, ללא צורך לחשוף את תוכנן. זה הדגם שבו משתמשות קבלות חשיפת מידע סלקטיבית בקנה מידה (התחייבויות מרקל, RFC 6962).
קבלות קריפטוגרפיות מעניקות לסוכני בינה מלאכותית מסלול ביקורת שהוא:
הן אינן תחליף לאימות קלט, לאכיפת מדיניות או לתשתית זהות. הן בסיס לשכבות אלו. כשאתם מפעילים סוכנים בעומסי עבודה מפוקחים, זרימות עבודה מרובות ארגונים, או כל סביבה שבה אין להניח שמבקר עתידי ייתן בכם אמון, הקבלות הן הדרך להפוך את מסלול הביקורת לגלוי והגון.
הלקח החשוב ביותר: קבלות מוכיחות מי אמר מה ומתי. אינן מוכיחות שאמור היה להיות נכון או אמת. החזקו בהבחנה הזו חזק. זו ההבדל בין מערכת מקוריות הוגנת לבין אחת מטעית.
כשאתם מוכנים לעבור משיעור זה להפעלת סוכנים חתומים על קבלות בסביבה אמיתית:
https://your-org.example.com/.well-known/agent-keys.json.הצטרף ל-Microsoft Foundry Discord כדי להיפגש עם לומדים אחרים, להשתתף בשעות משרד ולקבל תשובות על שאלות סוכני AI.
השיעור הזה מכסה חתימת קבלה בודדת ורצפים בשרשרת חשיש. אותם פרימיטיבים מרכיבים דגמים מתקדמים נוספים שאולי תפגוש ככל שמצב הממשל שלך יתפתח:
authorization_*) ואחרי ביצוע (result_*) עם חתימות נפרדות, שימושי כשהחלטת ההרשאה והתוצאה הנצפית נוצרות על ידי שחקנים שונים או בזמנים שונים. זה בונה על פורמט הקבלה הנלמד בשיעור זה.result_hash. מטענים בעולם האמיתי לעיתים עשירים יותר מתוצאת קריאה בודדת: נימוק לפני ההחלטה (תחזית המודל, אפשרויות שנשקלו, ראיות ושלמותן, מצב סיכון, שרשרת אחריות, תוצאת שער) יכולים כולם להיות בתוך המטען, אוטמים בפורמט קבלה בודד. זה שומר על פורמט הקבלה מינימלי תוך שמאפשר אבולוציה של סכמות מטען תחומה לפי תחום.signature.alg יכול לשאת ML-DSA-65 (תקן חתימות פוסט-קוואנטום של NIST) כשצריך לעבור. תכנן לפרק מעבר שבו הקבלות חתומות בשני מפתחות.(ייקבע על ידי מחזיקי תוכן הלימוד)
כתב ויתור: מסמך זה תורגם באמצעות שירות תרגום אוטומטי Co-op Translator. למרות שאנו שואפים לדיוק, יש לקחת בחשבון שתרגומים אוטומטיים עלולים להכיל שגיאות או אי-דיוקים. יש להחשיב את המסמך המקורי בשפתו הטבעית כמקור הסמכות. למידע קריטי מומלץ להשתמש בתרגום מקצועי על ידי מתרגם אדם. אנו לא אחראים לכל אי-הבנה או פירוש שגוי הנובע מהשימוש בתרגום זה.