Peržiūrėkite pamokos vaizdo įrašą: AI agentų apsauga su kriptografinėmis kvitomis
(Pamokos vaizdo įrašą ir miniatiūrą pridės Microsoft turinio komanda po sujungimo, atitinkančią pamokų 14 / 15 schemą.)
Šioje pamokoje aptarsime:
Baigę šią pamoką žinosite, kaip:
Įsivaizduokite, kad įdiegėte AI agentą Contoso kelionėms. Agentas skaito klientų užklausas, kreipiasi į skrydžių API ieškodamas variantų ir rezervuoja vietas kliento vardu. Praėjusį ketvirtį agentas apdorojau 50 000 rezervacijų.
Šiandien atvyksta auditorius. Jie užduoda paprastą klausimą: „Parodykite, ką jūsų agentas darė.“
Jūs pateikiate žurnalo failus. Auditorius juos peržvelgia ir užduoda sunkesnį klausimą: „Kaip aš žinau, kad šie žurnalai nebuvo redaguoti?“
Tai yra audito istorijos problema. Dauguma agentų įdiegimų šiandien naudoja:
Joks iš jų negali atsakyti auditoriaus klausimo be to, kad auditorius pasėtų kam nors pasitikėti (jums, jūsų debesų tiekėjui, duomenų bazės tiekėjui). Vidiniam naudojimui toks pasitikėjimas dažnai yra priimtinas. Reguliuojamoje veikloje (finansai, sveikatos apsauga, bet kas pagal ES AI aktą) taip nėra.
Kriptografinės kvitos šią problemą sušvelnina pateikiant pasirašytą pareiškimą, kurį galima nepriklausomai patikrinti. Auditorius vis tiek turi turėti nejudinamos priežasties pasitikėti pasirašymo raktu ir komponentu, pagaminusiu pareiškimą. Įterptas raktas įrodo tik savitarpiškumą, ne leidėjo tapatybę ar vykdymo vietą.
Kvita yra JSON objektas, kuris įrašo, ką agentas padarė, pasirašytas skaitmeniniu parašu.
flowchart LR
A[Agent invokes a tool] --> B[Build receipt payload]
B --> C[Canonicalize JSON RFC 8785]
C --> E[Ed25519 sign canonical bytes]
E --> F[Receipt with signature]
F --> G[Auditor verifies offline]
G --> H{Signature valid?}
H -- yes --> I[Signed statement is intact]
H -- no --> J[Receipt rejected]
Minimalioji kvita atrodo taip:
{
"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..."
}
}
Trys savybės atlieka darbą:
Parašas. Kvita pasirašyta agento vartų naudojamu Ed25519 privačiu raktu. Bet kas su atitinkamu viešuoju raktu gali neprisijungus patikrinti parašo autentiškumą. Patikimas asmuo privalo užfiksuoti šį raktą arba gauti jį iš patikimų registrų už kvitos ribų. Bet koks parašu pasirašytos srities klastojimas paneigia parašą.
Kanoninis kodavimas. Prieš pasirašant kvita serializuojama naudojant JSON Kanonizavimo Schemos (JCS, RFC 8785). Tai užtikrina, kad dvi įgyvendinimo rūšys, gaminančios tą pačią logišką kvitą, duoda bitų atitikmenį duomenis. Be kanonizavimo, skirtingi JSON serializatoriai gamintų skirtingus parašus tam pačiam turiniui.
Hash sujungimas. previous_receipt_hash laukas susieja kiekvieną kvitą su ankstesne. Pašalinus arba pakeitus vietą vienai kvitai, sugriūva pristatyta seka. Truncavimo arba rakto turėtojo valdomo perrašymo nustatymui taip pat reikalingas užfiksuotas dabartinis grandinės galas, nepriklausomas liudininkas arba patvirtinta uždaro populiacijos įsipareigojimo sistema.
Kartu šios savybės suteikia tris garantijas:
Kvitos sukūrimui nereikia specialios bibliotekos. Kriptografiniai primityvai yra plačiai prieinami, o logika telpa į kelias dešimtis Python eilučių.
Praktinės užduotys code_samples/18-signed-receipts.ipynb veda per visą procesą. Versijos kontrolės dokumentas code_samples/18-signed-receipts.execution-receipt.json fiksuoja tikslią užrašų knygutės maišos vertę, Python aplinką, cell skaičių ir patikrinimus nuo paskutinio švaraus paleidimo, nekeisdamas pamokos išsaugotų rezultatų. Santrauka:
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize # RFC 8785 canonical JSON
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()}"
# Generate or load a signing key (in production, store in a key vault)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
TRUSTED_PUBLIC_KEYS = {b64url_nopad(bytes(verify_key))} # pinned out of band by the verifier
# Build the receipt payload (no signature yet)
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,
}
# Canonicalize and sign the JCS bytes directly. PureEdDSA hashes internally.
canonical_bytes = canonicalize(payload)
signature_bytes = signing_key.sign(canonical_bytes).signature
# Attach a structured signature object.
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
Tai visas pasirašymo procesas. Pamokos užduotys peržvelgia kiekvieną žingsnį.
Tikrinimas yra atvirkštinė operacija:
import base64
import hashlib
from nacl import signing
from nacl.exceptions import CryptoError
from jcs import canonicalize
def b64url_decode(s: str) -> bytes:
padding = "=" * ((4 - len(s) % 4) % 4)
return base64.b64decode(s + padding, altchars=b"-_", validate=True)
def verify_receipt(receipt: dict, trusted_public_keys: set[str]) -> bool:
# The signature is a structured object: {"alg", "sig", "public_key"}.
if not isinstance(receipt, dict):
return False
sig_obj = receipt.get("signature")
if not isinstance(sig_obj, dict) or sig_obj.get("alg") != "EdDSA":
return False
try:
# An inline key proves only self-consistency. Trust is configured out of band.
public_key = sig_obj.get("public_key")
signature = sig_obj.get("sig")
if not isinstance(public_key, str) or not isinstance(signature, str):
return False
if public_key not in trusted_public_keys:
return False
payload = {k: v for k, v in receipt.items() if k != "signature"}
canonical_bytes = canonicalize(payload)
verify_key = signing.VerifyKey(b64url_decode(public_key))
verify_key.verify(canonical_bytes, b64url_decode(signature))
return True
except (CryptoError, TypeError, ValueError, KeyError, OverflowError):
return False
Ši funkcija priima kvitą ir patikimo asmens užfiksuotų raktų rinkinį bei grąžina
True tik jei parašas galioja priimtino rakto atžvilgiu. Tikrinimui nereikia
tinklo skambučio, bet rakto patikimumas vis tiek yra išorinės konfigūracijos sprendimas.
Kad pamatytumėte klastojimo aptikimą veiksme, užduotyje žingsniuojama per:
tool_args_hash lange.Tai praktiškas įrodymas, kad kvitos yra atsparios klastojimui: bet kokia modifikacija, kad ir maža, nebegalioja parašo.
Viena pasirašyta kvita saugo vieną veiksmą. Kvitos grandinė saugo seką.
flowchart LR
R0[Receipt 0<br/>genesis] --> R1[Receipt 1]
R1 --> R2[Receipt 2]
R2 --> R3[Receipt 3]
R1 -. previous_receipt_hash .-> R0
R2 -. previous_receipt_hash .-> R1
R3 -. previous_receipt_hash .-> R2
Kiekviena kvita įrašo ankstesnės kvitos maišą. Norint tyliai pašalinti antrą kvitą, piktadarys turėtų arba:
previous_receipt_hash lauką (sugriauna trečios kvitos parašą), ARBAJei privatus raktas yra apsaugotas ir tikrintojas užfiksuoja nepriklausomai liudytą grandinės galą, šie atakos būdai yra aptinkami. Rakto skelbimas kiekvienoje kvitoje nėra pakankamas: pasirašytojas arba piktadarys gali pateikti kitą raktą, perrašyti galūnę ar trumpinti grandinę, nebent patikimas asmuo turi išorinį pasitikėjimo ir išbaigtumo užtikrinimą.
Užduotyje žingsniuojama per:
previous_receipt_hash atitinka ankstesnio kvito tikrąjį hash.Taip sukuriate audito kelią, kurį išorinis auditas gali patikrinti naudodamas savo prizę emitento raktų ir grandinės kontrolinių taškų, nepasikliaudamas jūsų keičiama žurnalo saugykla.
Tai svarbiausia šio pamokos dalis. Kvitu yra galingi, bet jų galia ribota.
Kvitai įrodo tris dalykus:
Kvitai neįrodo:
policy_id buvo iš tikrųjų įvertinta arba kad ji būtų leidusi šį veiksmą, jei būtų patikrinta. Kvitas įrašo tai, ką teigta, o ne tai, kas buvo įgyvendinta.Ši riba yra svarbi dėl dviejų priežasčių:
Dažna klaida yra manyti, kad „turime kvitus“ reiškia „esame valdžioje.“ Ne. Kvitas yra pagrindas. Valdymas yra sistema, kurią kuriate virš jų.
3 punktas aukščiau vertas atskiros dalies: veiksmo kvitas sako „šis raktas pasirašė šį turinį,“ niekada „žmogus patvirtino šį veiksmą.“ Dėl didelės rizikos veiksmų (grąžinimų, ištrynimų, pervedimų) atskiras patvirtintojas gali pasirašyti tikslų veiksmą. To rakto su žmogumi susiejimas ir žmogaus dalyvavimo tikrinimas lieka atskiros atsakomybės.
Tolimesniame užrašyne code_samples/human-authorization-receipts.ipynb pridedama antrojo kvito rūšis, human.approval.v1, tokio pat formato kaip pamokos kvitai (tipizuotas duomenų krepšelis pasirašytas Ed25519 virš kanoninių JCS baitų, su signature objektu už pasirašytų baitų ribų). Patvirtintojo raktas pasirašo pilną kanoninį veiksmą ir jo santrauką; agento raktas pasirašo teiginį nešiojantį tą patį veiksmų santrauką ir parent_approval_ref, tai yra receipt_hash patvirtinimo, ta pati konvencija kaip previous_receipt_hash grandinėje, kurią sukūrėte aukščiau. Vienas verify_chain apdoroja abu artefaktus pagal teisės aktų atskirus piniginius raktų registrus (patvirtinančiųjų raktai prieš agentų raktus), todėl kodo kelias yra bendras, bet institucijos niekada nesimaišo.
Ši savybė, atsargiai suformuluota: prisegtas patvirtintojo raktas pasirašė būtent šį veiksmą, o agento raktas vėliau pasirašė pareiškimą nešiojantį tą patį veiksmų santrauką. Tai naudingi įrodymai, bet savaime nėra įrodymas, kad veiksmas buvo įvykdytas. Norint valdyti vykdymą, tikslus patvirtinimas privalo būti rezervuotas ir sunaudotas kartą ties tikrojo vykdytojo riba; nežinomi paslaugų teikėjo rezultatai turi likti nežinomi, o ne būti aklai pakartotinai bandyti. Užrašyno netinkamumo testai tikrina kvitų grandinės sluoksnį:
Kiekviena klaida atmetama su skirtinga priežastimi, todėl auditorius galintis atskirti, ar teisė paseno, ar teigiamas veiksmas pasikeitė. Patikrinimas yra tik skaitymui ir pakartotinas: jis nesunaudoja patvirtinimo ar nekviečia paslaugos teikėjo. Atsekamasis vykdytojas reikalauja patvarios, atominės pakartotinio paleidimo apsaugos ir aiškių sėkmės, nesėkmės ar neaiškių rezultatų. Žmogaus patvirtinimo kvitas yra edukacinė sudėtis, apibrėžta šioje pamokoje, ne kvitų rūšis, aprašyta draft-farley-acta-signed-receipts.
Šios pamokos Python kodas sąmoningai minimalus, kad galėtumėte perskaityti kiekvieną eilutę ir tiksliai suprasti, kas vyksta. Gamyboje turite dvi galimybes:
Kurti tiesiogiai ant kriptografinių pradinukų. 50 eilučių, kurias matėte aukščiau, yra pakankama daugeliui naudojimo atvejų. PyNaCl (Ed25519) ir jcs paketas (kanoninis JSON) yra gerai prižiūrimi ir auditavimi bibliotekos.
Naudoti gamybinę kvitų biblioteką. Keletas atvirojo kodo projektų įgyvendina tą patį modelį su papildomomis funkcijomis (raktų sukimasis, grupinis tikrinimas, JWK rinkinio platinimas, integracija su politikos varikliais):
draft-farley-acta-signed-receipts, pakeitimas 02). Šios pamokos plokščias edukacinis kvitas skiriasi nuo projekto {payload, signature} voko ir nėra pateiktas kaip atitinkantis įgyvendinimas. Projektas publikuoja bendrą atitikties rinkinį (agent-governance-testvectors) įgyvendinimams, taikantiems jo laidininko formatą.protect-mcp (npm) ir @veritasacta/verify (npm) paketai suteikia Node pagrindu veikiantį kvitų pasirašymo ir neprisijungus tikrinimo įgyvendinimą, skirtą apvynioti bet kurį MCP serverį tamperį atpažįstančiu audito keliu, įskaitant laikymo kartu srautą, kuriame sustabdytas veiksmas išskiria patvirtinimo kvitą, susietą su veiksmo santrauka (WebAuthn palaikoma darbalaukio eigoje), tokia pati patvirtinimo kvitų schema kaip žmogiško patvirtinimo užrašyne aukščiau.pip install nobulex) pateikia tą patį Ed25519 + JCS pasirašymo modelį Python kalboje su LangChain ir CrewAI integracijomis, įskaitant paskelbtus kryžminius patvirtinimo testų vektorius ir atitikties žemėlapį, prisidėtą per OWASP PR #2210.Sprendimas tarp savo kūrimo ir bibliotekos naudojimo atspindi sprendimą tarp savo JWT bibliotekos rašymo ir išbandytos bibliotekos naudojimo: abu yra pagrįsti; biblioteka taupo laiką ir sumažina audito paviršių; nuo nulio sukūrimas priverčia suprasti kiekvieną primityvą. Ši pamoka moko nuo nulio, kad turėtum pagrindą bet kuriam pasirinkimui.
Patikrinkite savo supratimą prieš pradėdami praktinę užduotį.
1. Kvitą pasirašo agente privatus Ed25519 raktas. Auditorius turi tik viešąjį raktą. Ar auditorius gali neprisijungęs patikrinti kvitą?
2. Užpuolikas pakeičia policy_id lauką kvite, teigdamas, kad jis buvo valdomas laisvesnės politikos. Parašas buvo ant originalaus paketo. Kas nutinka tikrinant?
3. Kodėl kvitas apima tool_args_hash ir result_hash vietoje žaliųjų argumentų ir rezultato?
4. previous_receipt_hash laukas sujungia kiekvieną kvitą su jo pirmtaku. Jei užpuolikas tyliai pašalina vieną kvitą grandinės viduryje, kas tampa negaliojančiu?
5. Kvitas patikrintas be problemų. Ar tai įrodo, kad agente veiksmas buvo teisingas, pagrįstas ar atitinkantis politiką?
Atidarykite code_samples/18-signed-receipts.ipynb ir užbaikite visus keturis skyrius:
Iššūkis 1: išplėskite kvito schemą papildomu lauku, kurį pasirenkate (pavyzdžiui, užklausos ID stebėjimui), atnaujinkite kanoninį pasirašymo logiką, kad jį įtrauktumėte, ir patvirtinkite, kad kvitas vis dar tinkamai tikrinamas. Tada pakeiskite tą lauką po pasirašymo ir patvirtinkite, kad tikrinimas nepavyksta. Tai privers jus suprasti, kaip kiekvienas kanoninio kodavimo baitas prisideda prie parašo.
Iššūkis 2: Sumaišykite SHA-256 dviejų kvitų duomenis kartu (sujungdami jų kanoninius baitus deterministine tvarka) ir įterpkite gautą maišą kaip naują lauką trečiame kvite prieš jį pasirašydami. Patikrinkite, kad visi trys kvitai vis dar tinkamai tikrinami. Jūs ką tik sukūrėte vieno žingsnio įtraukimo įrodymą: bet kas, turintis trečią kvitą, gali įrodyti, kad pirmi du egzistavo jį pasirašant, neatskleisdamas jų turinio. Tai modelis, kurį plačiai naudoja selektyvios atskleidimo kvitai (Merkle įsipareigojimai, RFC 6962).
Kriptografiniai kvitai suteikia DI agentams audito taką, kuris yra:
Jie nėra įvesties patikrinimo, politikos vykdymo, vykdytojo priėmimo, rezultatų įrodymo, pilnumo kontrolės ar tapatybės infrastruktūros pakaitalas. Jie yra pagrindas tiems sluoksniams. Reguliuojamuose ar daugorganizaciniuose procesuose kvitai leidžia konkrečius išdavėjo pareiškimus nepriklausomai patikrinti auditoriaus savo pasitikėjimo konfigūracija.
Svarbiausias išmokimas: kvitai įrodo, kad konkretaus rakto turėtojas pasirašė konkretų turinį. Laiko žyma yra pasirašančiojo teiginys, nebent tai būtų papildomai laiko patvirtinta. Kvitas neįrodo, kad jo turinys buvo teisingas, kad veiksmas buvo vykdytas, ar kad įrašų rinkinys yra pilnas. Laikykite šias skirtis aiškiai.
Kai būsite pasiruošę pereiti nuo šios pamokos prie kvito pasirašančių agentų diegimo tikroje aplinkoje:
Prisijunkite prie Microsoft Foundry Discord, kad susitikti su kitais mokiniais, dalyvauti konsultacijose ir gauti atsakymus į jūsų DI agentų klausimus.
Ši pamoka apima vieno kvito pasirašymą ir hash-grandines eilutes. Tie patys primityvai sudaro keletą pažangesnių modelių, kuriuos galite sutikti savo valdymo pozicijos brendimo metu:
authorization_*) ir posvykdymo (result_*) dalis su nepriklausomais parašais, naudinga, kai autorizacijos sprendimą ir stebėtą rezultatą sukuria skirtingi veikėjai ar skirtingu laiku. Tai papildomai sudedama virš šioje pamokoje mokomo kvito formato.result_hash. Realiojo pasaulio paketiniai duomenys dažnai yra turtingesni nei vieno įrankio kvietimo rezultatas: prieš sprendimą grindžiantis mąstymas (modelio prognozė, apsvarstyti variantai, įrodymai ir jų pilnumas, rizikos pozicija, atsakomybės grandinė, vartų sprendimas) gali gyventi viduje pakete, užklijuotas vienu kvitu. Tai laiko kvito formatą minimalų, leidžiant domenų pagal domenus evoliucionuoti paketų schemoms.alg žyma nėra migracija: profilis turi apibrėžti ML-DSA rakto kodavimą, kanonišką pasirašytų baitų formatą, patikrą, pasitikėjimo šalių politiką, atsparumą degradacijai ir perėjimo semantiką. Planuokite ir išbandykite dvigubo parašo perėjimą prieš bendrus vektorius prieš teigdami suderinamumą.Atsakomybės apribojimas: Šis dokumentas buvo išverstas naudojant dirbtinio intelekto vertimo paslaugą Co-op Translator. Nors siekiame tikslumo, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba laikomas autoritetingu šaltiniu. Svarbiai informacijai rekomenduojama naudoti profesionalų žmogiškąjį vertimą. Mes neatsakome už jokius nesusipratimus ar neteisingą interpretaciją, kilusią naudojantis šiuo vertimu.