Bekijk de lesvideo: AI-agenten beveiligen met cryptografische ontvangstbewijzen
(Lesvideo en thumbnail worden toegevoegd door het Microsoft-contentteam na samenvoeging, passend bij het patroon van les 14 / 15.)
Deze les behandelt:
Na het voltooien van deze les weet je hoe je:
Stel dat je een AI-agent hebt ingezet voor Contoso Travel. De agent leest klantverzoeken, roept een vlucht-API aan om opties op te zoeken, en boekt stoelen namens de klant. Vorig kwartaal heeft de agent 50.000 boekingen verwerkt.
Vandaag komt een auditor langs. Hij stelt een eenvoudige vraag: “Laat zien wat je agent heeft gedaan.”
Je levert je logbestanden aan. De auditor kijkt ernaar en stelt de lastiger vraag: “Hoe weet ik dat deze logs niet zijn bewerkt?”
Dit is het audit-trail probleem. De meeste agentinzettingen vertrouwen vandaag op:
Geen van deze kan de vraag van de auditor beantwoorden zonder dat de auditor iemand moet vertrouwen (jou, je cloudprovider, je databaseleverancier). Voor intern gebruik is dat vertrouwen vaak acceptabel. Voor gereguleerde workloads (financiën, gezondheidszorg, alles onder de EU AI Act) is dat niet zo.
Cryptografische ontvangstbewijzen lossen dit op door elke actie van de agent onafhankelijk verifieerbaar te maken. De auditor hoeft jou niet te vertrouwen. Hij heeft alleen jouw publieke sleutel en het ontvangstbewijs zelf nodig.
Een ontvangstbewijs is een JSON-object dat vastlegt wat een agent heeft gedaan, ondertekend met een digitale handtekening.
flowchart LR
A[Agent roept een tool aan] --> B[Bouw ontvangstbewijs payload]
B --> C[Canonicaliseer JSON RFC 8785]
C --> E[Ed25519 onderteken canonieke bytes]
E --> F[Ontvangstbewijs met handtekening]
F --> G[Auditor verifieert offline]
G --> H{Geldige handtekening?}
H -- yes --> I[Bewijs van kleverigheid]
H -- no --> J[Ontvangstbewijs afgewezen]
Een minimaal ontvangstbewijs ziet er zo uit:
{
"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..."
}
}
Drie eigenschappen doen het werk:
De handtekening. Het ontvangstbewijs is ondertekend door de gateway van de agent met een Ed25519 privé-sleutel. Iedereen met de bijbehorende publieke sleutel kan de handtekening offline verifiëren. Manipulatie van welk veld dan ook maakt de handtekening ongeldig.
Canonieke codering. Voor het ondertekenen wordt het ontvangstbewijs geserialiseerd met de JSON Canonicalization Scheme (JCS, RFC 8785). Dit zorgt ervoor dat twee implementaties die hetzelfde logische ontvangstbewijs produceren, exact dezelfde bytes outputten. Zonder canonisatie zouden verschillende JSON-serializers verschillende handtekeningen voor dezelfde inhoud genereren.
Hasj-ketting. Het veld previous_receipt_hash koppelt elk ontvangstbewijs aan het voorgaande. Het verwijderen of herschikken van een ontvangstbewijs breekt elk ontvangstbewijs dat daarna komt. Manipulatie wordt zichtbaar op ketenniveau, ook als individuele handtekeningen omzeild worden.
Samen bieden deze eigenschappen drie garanties:
Je hebt geen speciale bibliotheek nodig om een ontvangstbewijs te maken. De cryptografische primitieve functies zijn breed beschikbaar en de logica is enkele tientallen regels Python.
De hands-on oefeningen in code_samples/18-signed-receipts.ipynb lopen de volledige flow door. De samenvattende versie:
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize # RFC 8785 canonieke 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()}"
# Genereer of laad een ondertekeningssleutel (sla in productie op in een sleutelkluizen)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
# Bouw de ontvangst-payload (nog geen handtekening)
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,
}
# Canoniseer en onderteken de JCS-bytes direct. PureEdDSA genereert intern hashes.
canonical_bytes = canonicalize(payload)
signature_bytes = signing_key.sign(canonical_bytes).signature
# Voeg een gestructureerd handtekeningobject toe.
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
Dat is de volledige ondertekeningspipeline. De oefeningen in het notebook behandelen elke stap.
Verificatie is de inverse bewerking:
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:
# De handtekening is een gestructureerd object: {"alg", "sig", "public_key"}.
sig_obj = receipt.get("signature")
if not sig_obj or sig_obj.get("alg") != "EdDSA":
return False
# Reconstrueer de payload die daadwerkelijk is ondertekend (alles behalve de handtekening).
payload = {k: v for k, v in receipt.items() if k != "signature"}
canonical_bytes = canonicalize(payload)
try:
verify_key = signing.VerifyKey(b64url_decode(sig_obj["public_key"]))
verify_key.verify(canonical_bytes, b64url_decode(sig_obj["sig"]))
return True
except BadSignatureError:
return False
Deze functie neemt een ontvangstbewijs en geeft True terug als de handtekening geldig is, anders False. Geen netwerkoproep, geen service-afhankelijkheid, geen vertrouwen in een derde partij vereist.
Om manipulatie-detectie in actie te zien, loopt het notebook door:
tool_args_hash.Dit is de praktische demonstratie dat ontvangstbewijzen manipulatiebestendig zijn: elke wijziging, hoe klein ook, breekt de handtekening.
Een enkel ondertekend ontvangstbewijs beschermt één actie. Een keten van ontvangstbewijzen beschermt een reeks.
flowchart LR
R0[Ontvangst 0<br/>genese] --> R1[Ontvangst 1]
R1 --> R2[Ontvangst 2]
R2 --> R3[Ontvangst 3]
R1 -. previous_receipt_hash .-> R0
R2 -. previous_receipt_hash .-> R1
R3 -. previous_receipt_hash .-> R2
Elk ontvangstbewijs registreert de hasj van het ontvangstbewijs ervoor. Om ontvangstbewijs 2 stilletjes te verwijderen, zou een aanvaller moeten:
previous_receipt_hash van ontvangstbewijs 3 wijzigen (breekt de handtekening van ontvangstbewijs 3), OFAls de privé-sleutel in een hardware key vault zit en je publiceert de publieke sleutel met elk ontvangstbewijs, is geen van deze aanvallen uitvoerbaar zonder detectie.
Het notebook behandelt:
previous_receipt_hash van elk ontvangstbewijs overeenkomt met de echte hash van het voorgaande ontvangstbewijs.Zo produceer je een auditspoor dat een externe auditor kan verifiëren zonder jou te vertrouwen.
Dit is het belangrijkste gedeelte van deze les. Ontvangstbewijzen zijn krachtig maar hun kracht is begrensd.
Ontvangstbewijzen bewijzen drie dingen:
Ontvangstbewijzen bewijzen NIET:
policy_id wordt genoemd daadwerkelijk is geëvalueerd, of dat het deze actie zou hebben toegestaan als het was gecontroleerd. Het ontvangstbewijs registreert wat werd geclaimd, niet wat werd afgedwongen.Deze grens is belangrijk om twee redenen:
Een veelgemaakte fout is te denken dat “we ontvangstbewijzen hebben” betekent “we zijn gereguleerd.” Dat is niet zo. Ontvangstbewijzen zijn een fundament. Governance is het systeem dat je daarop bouwt.
Punt 3 hierboven verdient een eigen sectie: een actie-ontvangstbewijs zegt “deze sleutel heeft deze inhoud ondertekend,” nooit “een mens heeft dit geautoriseerd.” Voor risicoacties (terugbetalingen, verwijderen, overboekingen) vereisen governancekaders steeds vaker precies die ontbrekende verklaring, en dat is produceerbaar met dezelfde primitieve functies die je in deze les al hebt gebouwd.
Het vervolgnotebook code_samples/human-authorization-receipts.ipynb voegt een tweede soort ontvangstbewijs toe, human.approval.v1, in dezelfde envelopvorm als de ontvangstbewijzen van deze les (een getypede lading ondertekend door Ed25519 over zijn canonieke JCS-bytes, met het signature object buiten de ondertekende bytes). Een benoemde goedkeurder ondertekent de volledige canonieke actie en de digest ervan voor uitvoering; het actie-ontvangstbewijs van de agent draagt dezelfde actie-digest en een parent_approval_ref, de receipt_hash van de goedkeuring, dezelfde conventie als previous_receipt_hash in de keten die je hierboven hebt gebouwd. Eén verify_chain controleert beide artefacten onder afzonderlijke vastgepinde sleutelregisters (goedkeurderssleutels vs agentsleutels), zodat het codepad gedeeld is maar de autoriteiten nooit.
De eigenschap die dit koopt, zorgvuldig geformuleerd: de mens keurde precies deze actie goed, en de agent voerde precies die goedgekeurde actie uit. De weigeringtests in het notebook maken deze eigenschap echt in plaats van alleen bewerend:
Elke fout weigert met een andere reden, zodat een auditor die een weigering leest kan zien of autoriteit verlopen is of de uitgevoerde actie is veranderd. De regel die het notebook leert: een getekende goedkeuring is niet op zichzelf autoriteit. Autoriteit bestaat alleen als beide ontvangstbewijzen nog binden aan dezelfde canonieke actie op het moment van uitvoering. Het menselijke goedkeurings-ontvangstbewijs is een educatieve compositie gedefinieerd in deze les, niet een ontvangstbewijssoort gedefinieerd door draft-farley-acta-signed-receipts.
De Python-code in deze les is bewust minimaal zodat je elke regel kunt lezen en precies begrijpt wat er gebeurt. In productie heb je twee opties:
Bouw direct op de cryptografische primitieven. De 50 regels die je hierboven zag zijn voldoende voor veel toepassingen. PyNaCl (Ed25519) en het jcs-pakket (canonieke JSON) zijn goed onderhouden en beoordeelde bibliotheken.
Gebruik een productiebibliotheek voor ontvangstbewijzen. Verschillende open-source projecten implementeren hetzelfde patroon met extra functies (sleutelrotatie, batchverificatie, JWK Set distributie, integratie met beleidsmotoren):
draft-farley-acta-signed-receipts, revisie 02). Het educatieve, vlakke ontvangstbewijs van deze les wijkt af van de draft’s {payload, signature} envelop en wordt niet als conforme implementatie gepresenteerd. De draft publiceert een gedeelde conformiteitsset (agent-governance-testvectors) voor implementaties gericht op het wireformaat.protect-mcp (npm) en @veritasacta/verify (npm) pakketten bieden een Node-gebaseerde implementatie van ontvangstbewijs-ondertekening en offline verificatie, bedoeld om elke MCP-server te omwikkelen met een manipulatiebestendig auditspoor, waaronder een de facto holding-flow waarin een gepauzeerde actie een goedkeuringsbewijs emitteert gebonden aan de actie-digest (WebAuthn-ondersteund in de desktopflow), hetzelfde goedkeuringsbewijs patroon als het menselijke autorisatie-notebook hierboven.pip install nobulex) biedt hetzelfde Ed25519 + JCS ondertekeningspatroon in Python met LangChain en CrewAI-integraties, inclusief gepubliceerde cross-validatie testvectoren en een compliance-mapping bijgedragen via OWASP PR #2210.De keuze tussen zelf bouwen en een bibliotheek gebruiken weerspiegelt de keuze tussen zelf een JWT-bibliotheek schrijven en een geteste gebruiken: beide zijn redelijk; de bibliotheek bespaart tijd en verkleint auditoppervlak; zelf bouwen dwingt je elke primitieve te begrijpen. Deze les leert de zelfbouwroute zodat je de basis hebt voor beide keuzes.
Test je begrip voordat je doorgaat naar de praktijkopdracht.
1. Een ontvangstbewijs wordt ondertekend met de privé Ed25519-sleutel van de agent. De auditor heeft alleen de publieke sleutel. Kan de auditor het ontvangstbewijs offline verifiëren?
2. Een aanvaller wijzigt het veld policy_id van een ontvangstbewijs om te beweren dat het werd beheerst door een soepeler beleid. De handtekening was over de originele lading. Wat gebeurt er tijdens verificatie?
3. Waarom bevat het ontvangstbewijs een tool_args_hash en result_hash in plaats van de ruwe argumenten en het resultaat?
4. Het veld previous_receipt_hash koppelt elk ontvangstbewijs aan zijn voorganger. Wat wordt ongeldig als een aanvaller stilletjes één ontvangstbewijs uit het midden van een keten verwijdert?
5. Een ontvangstbewijs verifieert correct. Bewijst dat dat de actie van de agent correct, betrouwbaar of beleidsconform was?
Open code_samples/18-signed-receipts.ipynb en voltooi alle vier secties:
Uitdaging 1: breid het ontvangstbewijs-schema uit met een extra veld naar keuze (bijvoorbeeld een request-ID voor tracing), update de canonieke ondertekeningslogica om dit op te nemen, en bevestig dat het ontvangstbewijs nog steeds correct geverifieerd kan worden. Wijzig dan het veld na ondertekening en bevestig dat verificatie faalt. Dit dwingt je te begrijpen hoe elke byte van de canonieke codering bijdraagt aan de handtekening.
Uitdaging 2: SHA-256-hash twee van je ontvangstbewijzen samen (concateneer hun canonieke bytes in een deterministische volgorde) en embed de resulterende digest als een nieuw veld op een derde ontvangstbewijs voordat je het ondertekent. Verifieer dat alle drie ontvangstbewijzen nog steeds correct geverifieerd kunnen worden. Je hebt zojuist een één-stap inclusiebewijs gebouwd: iedereen met het derde ontvangstbewijs kan bewijzen dat de eerste twee bestonden op het moment van ondertekening, zonder hun inhoud te hoeven onthullen. Dit is het patroon dat selective-disclosure ontvangstbewijzen op schaal gebruiken (Merkle commitments, RFC 6962).
Cryptografische ontvangstbewijzen geven AI-agenten een audittrail die:
Ze zijn geen vervanging voor invoervalidatie, beleidsafdwinging of identiteitsinfrastructuur. Ze vormen een fundament voor die lagen. Wanneer je agenten inzet in gereguleerde workloads, multi-organisatie workflows, of elke situatie waar een toekomstige auditor je niet per se vertrouwt, zijn ontvangstbewijzen hoe je de audittrail eerlijk maakt.
De belangrijkste boodschap: ontvangstbewijzen bewijzen wie wat zei en wanneer. Ze bewijzen niet dat wat gezegd werd waar of juist was. Houd dat onderscheid goed vast. Het is het verschil tussen een eerlijk provenance-systeem en een misleidend systeem.
Wanneer je klaar bent om van deze les over te stappen naar het inzetten van ontvangstbewijs-ondertekende agenten in een echte omgeving:
https://your-org.example.com/.well-known/agent-keys.json.Word lid van de Microsoft Foundry Discord om andere lerenden te ontmoeten, office hours bij te wonen en je vragen over AI-agenten beantwoord te krijgen.
Deze les behandelt enkel-ontvangstbewijsondertekening en hash-ketenreeksen. Dezelfde primitieve vormen verschillende meer geavanceerde patronen die je tegenkomt als je governance volwassen wordt:
authorization_*) en post-executie (result_*) helften met onafhankelijke handtekeningen, nuttig wanneer de autorisatiebeslissing en het geobserveerde resultaat door verschillende actoren of op verschillende tijden geproduceerd worden. Dit bouwt additief voort op het ontvangstbewijsformaat uit deze les.result_hash zet. Payloads uit de praktijk zijn vaak rijker dan het resultaat van één tool-aanroep: pre-besluitvorming (modelvoorspelling, overwogen opties, bewijs en volledigheid, risicohouding, verantwoordingsketen, poortresultaat) kunnen allemaal in de payload zitten, afgesloten door één ontvangstbewijs. Dit houdt het ontvangstbewijsformaat minimaal en laat payload-schema’s domein-specifiek evolueren.signature.alg kan ML-DSA-65 dragen (de NIST post-quantum handtekeningsstandaard) wanneer migratie nodig is. Plan een overgangsperiode waarin ontvangstbewijzen dubbel worden ondertekend.Disclaimer: Dit document is vertaald met behulp van de AI vertaaldienst Co-op Translator. Hoewel we streven naar nauwkeurigheid, dient u er rekening mee te houden dat geautomatiseerde vertalingen fouten of onnauwkeurigheden kunnen bevatten. Het originele document in de oorspronkelijke taal moet worden beschouwd als de gezaghebbende bron. Voor kritieke informatie wordt professionele menselijke vertaling aanbevolen. Wij zijn niet aansprakelijk voor eventuele misverstanden of verkeerde interpretaties die voortvloeien uit het gebruik van deze vertaling.