Bekijk de lesvideo: AI-agents beveiligen met cryptografische ontvangstbewijzen
(Lesvideo en thumbnail worden na samenvoeging toegevoegd door het Microsoft contentteam, passend bij het patroon van les 14 / 15.)
Deze les behandelt:
Na het voltooien van deze les kun je:
Stel, je hebt een AI-agent ingezet voor Contoso Travel. De agent leest klantverzoeken, roept een vlucht-API aan om opties op te zoeken en boekt namens de klant stoelen. Vorig kwartaal verwerkte de agent 50.000 boekingen.
Vandaag komt een auditor. Hij stelt een simpele vraag: “Laat me zien wat je agent gedaan heeft.”
Je overhandigt je logbestanden. De auditor bekijkt ze en stelt de lastigere vraag: “Hoe weet ik dat deze logs niet zijn aangepast?”
Dit is het audit-trail-probleem. De meeste agentimplementaties vertrouwen tegenwoordig op:
Geen van deze kan de vraag van de auditor beantwoorden zonder dat die 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) niet.
Cryptografische ontvangstbewijzen lossen dit op door elke agentactie 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 gedaan heeft, ondertekend met een digitale handtekening.
flowchart LR
A[Agent roept een hulpmiddel aan] --> B[Opbouw ontvangstgegevens]
B --> C[Canonicaliseer JSON RFC 8785]
C --> D[SHA-256 hash]
D --> E[Ed25519 ondertekening]
E --> F[Ontvangst met handtekening]
F --> G[Auditor verifieert offline]
G --> H{Handtekening geldig?}
H -- ja --> I[Manipulatiebestendig bewijs]
H -- nee --> J[Ontvangst 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 private key. Iedereen met de bijbehorende publieke sleutel kan de handtekening offline verifiëren. Manipulatie van elk veld 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 byte-identieke output geven. Zonder canoniek maken zouden verschillende JSON-serializers verschillende handtekeningen geven voor dezelfde inhoud.
Hash-keten. Het veld previous_receipt_hash verbindt elk ontvangstbewijs met het voorgaande. Het verwijderen of herschikken van een ontvangstbewijs breekt elk daaropvolgend ontvangstbewijs. Manipulatie wordt dus zichtbaar op ketenniveau, zelfs als individuele handtekeningen worden omzeild.
Samen bieden deze eigenschappen drie garanties:
Je hebt geen speciale bibliotheek nodig om een ontvangstbewijs te maken. De cryptografische primitieve zijn breed beschikbaar en de logica is een paar dozijn regels Python.
De praktische oefeningen in code_samples/18-signed-receipts.ipynb doorlopen de volledige workflow. De samenvatting:
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 (in productie, opslaan in een sleutelkluis)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
# Bouw de ontvangstgegevens op (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,
}
# Canoniseren, hashen, ondertekenen.
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
signature_bytes = signing_key.sign(message_hash).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 hele ondertekeningspijplijn. De oefeningen in de notebook leggen elke stap uit.
Verificatie is de inverse operatie:
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 werd ondertekend (alles behalve de handtekening).
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
Deze functie neemt een ontvangstbewijs en geeft True terug als de handtekening geldig is, anders False. Geen netwerkverzoek, geen service-afhankelijkheid, geen vertrouwen in derde partijen vereist.
Om manipulatie detectie in actie te zien, doorloopt de notebook:
tool_args_hash aanpassen.Dit is de praktische demonstratie dat ontvangstbewijzen manipulatie-bestendig zijn: elke wijziging, hoe klein ook, verbreekt de handtekening.
Een enkel ondertekend ontvangstbewijs beschermt één actie. Een keten van ontvangstbewijzen beschermt een reeks.
flowchart LR
R0[Bon 0<br/>genesis] --> R1[Bon 1]
R1 --> R2[Bon 2]
R2 --> R3[Bon 3]
R1 -. previous_receipt_hash .-> R0
R2 -. previous_receipt_hash .-> R1
R3 -. previous_receipt_hash .-> R2
Elk ontvangstbewijs registreert de hash van het voorgaande ontvangstbewijs. Om ontvangstbewijs 2 stilletjes te verwijderen zou een aanvaller óf:
previous_receipt_hash-veld van ontvangstbewijs 3 aanpassen (maakt de handtekening van ontvangstbewijs 3 ongeldig), OFAls de private key in een hardware key vault zit en je publiceert de publieke sleutel bij elk ontvangstbewijs, is geen van beide aanvallen haalbaar zonder detectie.
De notebook doorloopt:
previous_receipt_hash overeenkomt met de actuele hash van het voorgaande ontvangstbewijs.Dit is hoe je een audit trail produceert die een externe auditor kan verifiëren zonder jou te hoeven vertrouwen.
Dit is het belangrijkste deel van deze les. Ontvangstbewijzen zijn krachtig, maar hun kracht is begrensd.
Ontvangstbewijzen bewijzen drie dingen:
Ontvangstbewijzen bewijzen NIET:
policy_id daadwerkelijk is geëvalueerd, of dat het deze actie zou hebben toegestaan als het zou zijn gecontroleerd. Het ontvangstbewijs legt vast wat werd beweerd, niet wat werd afgedwongen.Deze grens is belangrijk om twee redenen:
Een veelgemaakte fout is aannemen dat “we hebben ontvangstbewijzen” betekent “we hebben governance.” Dat is niet zo. Ontvangstbewijzen zijn een fundament. Governance is het systeem dat je erop bouwt.
De Python-code in deze les is bewust minimalistisch zodat je elke regel kunt lezen en precies begrijpt wat er gebeurt. In productie heb je twee opties:
Direct bouwen op cryptografische primitieve. De 50 regels hierboven zijn voldoende voor veel use cases. PyNaCl (Ed25519) en het jcs-pakket (canonieke JSON) zijn goed onderhouden en gecontroleerde bibliotheken.
Een productiebibliotheek voor ontvangstbewijzen gebruiken. Verschillende open-source projecten implementeren hetzelfde patroon met extra functies (sleutelrotatie, batchverificatie, JWK Set distributie, integratie met beleidsengines):
draft-farley-acta-signed-receipts) die momenteel in het standaardisatieproces zit.protect-mcp (npm) en @veritasacta/verify (npm) bieden een Node-implementatie van ontvangstbewijs ondertekening en offline verificatie, bedoeld om elke MCP-server te omhullen met een manipulatie-bestendige audit trail.De keuze tussen zelf bouwen en een bibliotheek gebruiken lijkt op het kiezen tussen zelf een JWT-bibliotheek schrijven of een geteste bibliotheek gebruiken: beide zijn acceptabel; de bibliotheek bespaart tijd en vermindert het audit-oppervlak; zelf bouwen dwingt je alle primitieve te begrijpen. Deze les leert de zelfbouw zodat je de basis voor beide opties hebt.
Test je begrip voordat je doorgaat naar de praktijkopdracht.
1. Een ontvangstbewijs is ondertekend met de private Ed25519-sleutel van de agent. De auditor heeft alleen de publieke sleutel. Kan de auditor het ontvangstbewijs offline verifiëren?
2. Een aanvaller past het veld policy_id van een ontvangstbewijs aan om te beweren dat een vrijgeviger beleid van toepassing was. De handtekening was over de originele payload. Wat gebeurt er bij verificatie?
3. Waarom bevat het ontvangstbewijs een tool_args_hash en result_hash in plaats van de ruwe argumenten en resultaten?
4. Het veld previous_receipt_hash verbindt elk ontvangstbewijs met zijn voorganger. Als een aanvaller stilletjes een ontvangstbewijs uit het midden van een keten verwijdert, wat wordt dan ongeldig?
5. Een ontvangstbewijs verifieert schoon. Bewijst dit dat de actie van de agent correct, juist, of beleidsconform was?
Open code_samples/18-signed-receipts.ipynb en voltooi alle vier secties:
Extra uitdaging 1: breid het ontvangstbewijs-schema uit met een extra veld naar keuze (bijvoorbeeld een request-ID voor tracing), werk de canonieke ondertekenlogica bij om dit op te nemen, en bevestig dat het ontvangstbewijs nog steeds correct door verificatie komt. Pas het veld vervolgens aan na ondertekening en bevestig dat verificatie faalt. Dit dwingt je te begrijpen hoe iedere byte van de canonieke codering bijdraagt aan de handtekening. Stretch-uitdaging 2: SHA-256-hash twee van je bonnen samen (concateneer hun canonieke bytes in een deterministische volgorde) en voeg de resulterende digest toe als een nieuw veld op een derde bon voordat je deze ondertekent. Verifieer dat alle drie de bonnen nog steeds correct kunnen worden teruggelezen. Je hebt zojuist een éénstaps-inclusiebewijs gebouwd: iedereen met de derde bon kan bewijzen dat de eerste twee bestonden op het moment dat deze werden ondertekend, zonder de inhoud ervan te hoeven onthullen. Dit is het patroon dat selectief openbaar gemaakte bonnen op grote schaal gebruiken (Merkle-commits, RFC 6962).
Cryptografische bonnen geven AI-agenten een audittrail die:
Ze zijn geen vervanging voor invoervalidatie, beleidsafhandeling of identiteitsinfrastructuur. Ze zijn een fundament voor die lagen. Wanneer je agenten inzet in gereguleerde workloads, multi-organisatie workflows, of elke omgeving waar een toekomstige auditor niet zomaar vertrouwen kan worden gegeven, zijn bonnen hoe je de audittrail eerlijk maakt.
De belangrijkste conclusie: bonnen bewijzen wie wat zei en wanneer. Ze bewijzen niet dat wat gezegd werd waar of correct was. Houd dat onderscheid strak 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 implementeren van met bonnen 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 leerlingen te ontmoeten, kantooruren bij te wonen en je AI-agentenvragen beantwoord te krijgen.
Deze les behandelt enkelvoudig bonondertekening en hash-gekoppelde reeksen. Dezelfde primitieve vormen componeren in meerdere geavanceerdere patronen die je kunt tegenkomen naarmate je governance-positie volwassen wordt:
authorization_*) en post-executie (result_*) helft met onafhankelijke handtekeningen, nuttig wanneer het autorisatiebesluit en het geobserveerde resultaat door verschillende actoren of op verschillende tijden worden geproduceerd. Dit bouwt voort op het bonformaat dat in deze les is geleerd.result_hash zet. Payloads uit de praktijk zijn vaak rijker dan een enkele tool call resultaat: pre-decisie redenering (modelvoorspelling, overwogen opties, bewijs en de volledigheid ervan, risicohouding, verantwoordingsketen, poortresultaat) kan allemaal in de payload zitten, verzegeld door één enkele bon. Dit houdt het bonformaat minimaal terwijl payloadschema’s domein-per-domein kunnen evolueren.signature.alg kan ML-DSA-65 dragen (de NIST post-quantum ondertekeningsstandaard) wanneer je moet migreren. Plan een overgangsperiode waarin bonnen dubbelondertekend zijn.Computer Use Agents bouwen (CUA)
(Wordt bepaald door de curriculumbeheerders)
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.