ai-agents-for-beginners

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.)

AI-agenten beveiligen met cryptografische ontvangstbewijzen

Inleiding

Deze les behandelt:

Leerdoelen

Na het voltooien van deze les weet je hoe je:

Het probleem: de audit-trail van je agent

Stel je voor 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 verwerkte de agent 50.000 boekingen.

Vandaag arriveert een auditor. Ze stellen een simpele vraag: “Laat me zien wat je agent deed.”

Je overhandigt je logbestanden. De auditor bekijkt ze en stelt de moeilijkere vraag: “Hoe weet ik dat deze logs niet bewerkt zijn?”

Dit is het probleem van de audit-trail. De meeste agentinzettingen vertrouwen tegenwoordig 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 die vertrouwensbasis vaak acceptabel. Voor gereguleerde workloads (financiën, gezondheidszorg, alles onder de EU AI-verordening) is dat niet zo.

Cryptografische ontvangstbewijzen lossen dit op door elke agentactie onafhankelijk verifieerbaar te maken. De auditor hoeft jou niet te vertrouwen. Ze hebben alleen je openbare sleutel en het ontvangstbewijs zelf nodig.

Wat is een cryptografisch ontvangstbewijs?

Een ontvangstbewijs is een JSON-object dat vastlegt wat een agent deed en ondertekend is met een digitale handtekening.

flowchart LR
    A[Agent roept een tool aan] --> B[Bouw ontvangstbewijs payload]
    B --> C[Canonicaliseer JSON RFC 8785]
    C --> D[SHA-256 hash]
    D --> E[Ed25519 ondertekenen]
    E --> F[Ontvangstbewijs met handtekening]
    F --> G[Auditor verifieert offline]
    G --> H{Handtekening geldig?}
    H -- yes --> I[Bewijs tegen manipulatie]
    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:

  1. De handtekening. Het ontvangstbewijs wordt ondertekend door de gateway van de agent met een Ed25519-private sleutel. Iedereen met de overeenkomende publieke sleutel kan de handtekening offline verifiëren. Manipulatie van enig veld maakt de handtekening ongeldig.

  2. Canonieke codering. Voor het ondertekenen wordt het ontvangstbewijs geserialiseerd via het JSON Canonicalization Scheme (JCS, RFC 8785). Dit zorgt dat twee implementaties die hetzelfde logische ontvangstbewijs produceren ook byte-identieke uitvoer geven. Zonder canonicalisatie zouden verschillende JSON-serializers verschillende handtekeningen voor dezelfde inhoud produceren.

  3. Hash-ketting. Het veld previous_receipt_hash koppelt elk ontvangstbewijs aan het vorige. Het verwijderen of herschikken van een ontvangstbewijs breekt elk ontvangstbewijs dat erop volgde. Manipulatie wordt zichtbaar op keteniveau, zelfs als individuele handtekeningen worden omzeild.

Samen bieden deze eigenschappen drie garanties:

Een ontvangstbewijs maken in Python

Je hebt geen speciale bibliotheek nodig om een ontvangstbewijs te maken. De cryptografische primitieve zijn breed beschikbaar en de logica is een paar tientallen regels Python.

De hands-on oefeningen in code_samples/18-signed-receipts.ipynb lopen de volledige flow door. 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 (bewaar deze in productie in een sleutelkluis)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key

# Bouw de ontvangstpayload (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 gehele ondertekeningspijplijn. De oefeningen in het notebook lopen elke stap na.

Een ontvangstbewijs verifiëren en manipulatie detecteren

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

    # Stel de payload die daadwerkelijk is ondertekend opnieuw samen (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, False anders. Geen netwerkoproep, geen service-afhankelijkheid, geen vertrouwen in derden nodig.

Om manipulatie detectie in actie te zien, loopt het notebook het volgende door:

  1. Een geldig ontvangstbewijs maken en bevestigen dat het verifieert.
  2. Één byte van het veld tool_args_hash wijzigen.
  3. De verificatie opnieuw uitvoeren en zien dat het faalt.

Dit is de praktische demonstratie dat ontvangstbewijzen manipulatie-bestendig zijn: elke wijziging, hoe klein ook, breekt de handtekening.

Ontvangstbewijzen aan elkaar ketenen voor multi-stap agents

Eén 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 vorige ontvangstbewijs. Om ontvangstbewijs 2 stiekem te verwijderen, zou een aanvaller moeten:

Als de private 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 loopt het volgende door:

  1. Een keten van drie ontvangstbewijzen bouwen.
  2. Verifiëren dat elk ontvangstbewijs zijn previous_receipt_hash overeenkomt met de werkelijke hash van het voorgaande ontvangstbewijs.
  3. Manipulatie aan één ontvangstbewijs in het midden en zien dat de keten op precies dat punt breekt.

Dit is hoe je een audit-trail maakt die een externe auditor kan verifiëren zonder jou te vertrouwen.

Wat ontvangstbewijzen bewijzen (en wat niet)

Dit is het belangrijkste deel van deze les. Ontvangstbewijzen zijn krachtig, maar hun kracht is begrensd.

Ontvangstbewijzen bewijzen drie dingen:

  1. Attribuering: een specifieke sleutel heeft een specifieke payload ondertekend.
  2. Integriteit: de payload is sinds ondertekening niet veranderd.
  3. Volgorde: dit ontvangstbewijs kwam na dat ontvangstbewijs in de hash-keten.

Ontvangstbewijzen bewijzen NIET:

  1. Correctheid: dat de actie van de agent de juiste actie was. Een ontvangstbewijs kan net zo goed voor een fout antwoord worden ondertekend als voor een juist antwoord.
  2. Beleidsnaleving: dat het beleid aangeduid in policy_id daadwerkelijk is geëvalueerd, of dat het deze actie zou hebben toegestaan als het gecontroleerd was. Het ontvangstbewijs registreert wat werd geclaimd, niet wat werd afgedwongen.
  3. Identiteit buiten de sleutel: het ontvangstbewijs zegt “deze sleutel heeft deze inhoud ondertekend.” Het zegt niet “deze mens heeft dit goedgekeurd.” Het koppelen van een sleutel aan een persoon of organisatie vereist een aparte identiteitsinfrastructuur (een directory, een public key registry, etc.).
  4. Waarheidsgetrouwheid van invoer: als de agent een gemanipuleerd prompt ontvangt en daarop handelt, registreert het ontvangstbewijs de actie nauwkeurig. Ontvangstbewijzen zijn downstream van invoercontrole, niet een vervanging daarvan.

Deze grens is belangrijk om twee redenen:

Een veelgemaakte vergissing 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 erop bouwt.

Bewijzen dat een mens de exacte actie goedkeurde

Punt 3 hierboven verdient een eigen paragraaf: een actie-ontvangstbewijs zegt “deze sleutel heeft deze inhoud ondertekend,” nooit “een mens heeft dit goedgekeurd.” Voor risicovolle acties (terugbetalingen, verwijderingen, overboekingen) vereisen governance-kaders steeds vaker precies die ontbrekende verklaring, en die is produceerbaar met dezelfde primitieve die je al in deze les bouwde.

Het vervolg-notebook code_samples/human-authorization-receipts.ipynb voegt een tweede ontvangstbewijs-type toe, human.approval.v1, in dezelfde envelopvorm als de ontvangstbewijzen in deze les (een getypte payload ondertekend met Ed25519 over de canonieke SHA-256, met het signature object buiten de ondertekende bytes). Een benoemde goedkeurder ondertekent de volledige canonieke actie en de digest daarvan vóór uitvoering; het actie-ontvangstbewijs van de agent draagt dezelfde actiedigest en een parent_approval_ref, de receipt_hash van de goedkeuring, dezelfde conventie als previous_receipt_hash in de keten die je hierboven bouwde. Eén verify_chain loopt over beide artefacten onder gescheiden pinned key registries (goedkeurderssleutels versus agentsleutels), zodat het codepad gedeeld is maar de autoriteiten nooit.

De eigenschap die dit oplevert, zorgvuldig geformuleerd: de mens keurde deze exacte actie goed, en de agent voerde precies die goedgekeurde actie uit. De geweigerde gevallen in het notebook maken deze eigenschap echt in plaats van verondersteld:

Elke fout weigert met een eigen reden, zodat een auditor die een weigering leest kan zien of autoriteit verlopen is of de uitgevoerde actie veranderde. De regel die het notebook leert: een ondertekende goedkeuring is op zichzelf geen autoriteit. Autoriteit bestaat alleen als beide ontvangstbewijzen op het moment van uitvoering nog aan dezelfde canonieke actie binden. Het co-handtekeningenpad in dezelfde Internet-Draft die deze les volgt (draft-farley-acta-signed-receipts) is de standaarden-track vorm van dit patroon.

Productiereferenties

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:

  1. Bouw direct op de cryptografische primitieve. De 50 regels die je hierboven zag zijn voldoende voor veel toepassingen. PyNaCl (Ed25519) en het jcs-pakket (canonieke JSON) zijn goed onderhouden en geauditte bibliotheken.

  2. Gebruik een productiebibliotheek voor ontvangstbewijzen. Diverse open-source projecten implementeren hetzelfde patroon met extra functies (sleutelrotatie, batchverificatie, JWK Set distributie, integratie met beleids-engines):

    • Het ontvangstbewijsformaat in deze les volgt een IETF Internet-Draft (draft-farley-acta-signed-receipts, revisie 02) die momenteel door de standaardisatieprocedure loopt, met een gedeelde conformance suite (agent-governance-testvectors) waar onafhankelijke implementaties cross-verificatie tegen uitvoeren voor byte-identieke canonieke uitvoer.
    • De Microsoft Agent Governance Toolkit combineert ontvangstbewijzen met Cedar-gebaseerde beleidsbeslissingen; zie Tutorial 33 in die repository voor een end-to-end voorbeeld.
    • De protect-mcp (npm) en @veritasacta/verify (npm) pakketten bieden een Node-implementatie van ontvangstbewijs-ondertekening en offline verificatie, bedoeld om elke MCP-server te voorzien van een manipulatie-bestendige audit-trail, inclusief een flow waarbij een gepauzeerde actie een goedkeuringsontvangstbewijs uitbrengt dat gebonden is aan de actiedigest (WebAuthn-ondersteund in de desktopflow), hetzelfde goedkeurings-bevestigingspatroon als het human-authorization-notebook hierboven.
    • De nobulex Python SDK (pip install nobulex) levert hetzelfde Ed25519 + JCS ondertekeningspatroon in Python met LangChain- en CrewAI-integraties, inclusief gepubliceerde cross-validatie testvectors 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 het audit-oppervlak; de zelfbouw-methode dwingt je om elke primitief te begrijpen. Deze les leert de zelfbouw-methode zodat je de basis hebt voor beide keuzes.

Kennischeck

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?

Antwoord Ja. Ed25519-verificatie vereist alleen de publieke sleutel en de ondertekende bytes. Geen netwerkoproep, geen service-afhankelijkheid. Dit is de eigenschap die ontvangstbewijzen nuttig maakt in air-gapped, multi-organisatie of laag-vertrouwens auditomgevingen.

2. Een aanvaller wijzigt het veld policy_id van een ontvangstbewijs om te claimen dat het werd geregeld door een minder streng beleid. De handtekening was over de originele payload. Wat gebeurt er bij verificatie?

Antwoord Verificatie mislukt. De handtekening werd berekend over de canonieke bytes van de originele gegevens; het aanpassen van een veld verandert de canonieke bytes, wat de SHA-256-hash verandert en daarmee de handtekening ongeldig maakt. De aanvaller zou de privésleutel nodig hebben om een nieuwe geldige handtekening te produceren, die hij niet heeft.

3. Waarom bevat de ontvangst een tool_args_hash en result_hash in plaats van de ruwe argumenten en het resultaat?

Antwoord Twee redenen. Ten eerste moet de ontvangst mogelijk worden gearchiveerd of verzonden in omgevingen waar het lekken van de ruwe inhoud (PII, bedrijfsgegevens) een probleem is. Hashing houdt de ontvangst klein en de inhoud privé; de auditor verifieert dat de hash overeenkomt met een apart opgeslagen kopie van de daadwerkelijke inhoud. Ten tweede hebben hashes een vaste grootte; een ontvangst met hashes is in grootte begrensd, ongeacht hoe groot de inputs en outputs waren.

4. Het veld previous_receipt_hash koppelt elke ontvangst aan zijn voorganger. Wat wordt ongeldig als een aanvaller stilletjes een ontvangst uit het midden van een keten verwijdert?

Antwoord Elke ontvangst die na de verwijderde kwam. Hun `previous_receipt_hash` velden corresponderen niet langer met de daadwerkelijke keten (omdat de ontvangst waarnaar verwezen werd niet meer bestaat, of de keten nu naar een andere voorganger wijst). Om de verwijdering te verbergen, zou de aanvaller elke latere ontvangst opnieuw moeten ondertekenen, wat de privésleutel vereist.

5. Een ontvangst verifieert correct. Bewijst dat dat de actie van de agent correct, juist of in overeenstemming met het beleid was?

Antwoord Nee. Een geldige ontvangst bewijst drie dingen: attributie (deze sleutel heeft deze inhoud ondertekend), integriteit (de inhoud is niet veranderd) en volgorde (deze ontvangst kwam na die ontvangst). Het bewijst NIET dat de actie correct was, dat het in `policy_id` genoemde beleid daadwerkelijk is geëvalueerd, of dat de agent elke regel heeft gevolgd. Ontvangsten maken het gedrag van de agent auditbaar, niet noodzakelijkerwijs correct. Dit is de belangrijkste grens in de les.

Oefening

Open code_samples/18-signed-receipts.ipynb en voltooi alle vier secties:

  1. Sectie 1: Onderteken je eerste ontvangst en verifieer deze.
  2. Sectie 2: Manipuleer de ontvangst en observeer dat verificatie mislukt.
  3. Sectie 3: Bouw een keten van drie ontvangsten en verifieer de integriteit van de keten.
  4. Sectie 4: Pas het patroon toe op een agent gebouwd met het Microsoft Agent Framework: wikkel een tool-aanroep in ontvangst-ondertekening en verifieer vervolgens de ontvangst onafhankelijk.

Uitdagende opdracht 1: breid het ontvangstschema uit met een extra veld naar keuze (bijvoorbeeld een verzoek-ID voor tracing), werk de canonieke ondertekeningslogica bij om dit op te nemen, en bevestig dat de ontvangst nog steeds correct door de verificatie gaat. Pas het veld vervolgens aan na ondertekening en bevestig dat verificatie faalt. Dit dwingt je te begrijpen hoe elke byte van de canonieke codering bijdraagt aan de handtekening.

Uitdagende opdracht 2: SHA-256-hash twee van je ontvangsten samen (concateneer hun canonieke bytes in een deterministische volgorde) en verwerk de resulterende digest als een nieuw veld in een derde ontvangst voordat je deze ondertekent. Verifieer dat alle drie de ontvangsten nog steeds correct door de verificatie gaan. Je hebt zojuist een één-stap inclusie bewijs gebouwd: iedereen die de derde ontvangst heeft, kan aantonen dat de eerste twee bestonden op het moment van ondertekening, zonder hun inhoud te hoeven onthullen. Dit is het patroon dat selective-disclosure ontvangsten op schaal gebruiken (Merkle-commits, RFC 6962).

Conclusie

Cryptografische ontvangsten geven AI-agenten een audittrail die:

Ze zijn geen vervanging voor invoervalidatie, beleidsafhandeling of identiteitsinfrastructuur. Ze vormen de basis voor die lagen. Wanneer je agenten inzet in gereguleerde workloads, multi-organisatie-workflows of elke situatie waarin een toekomstige auditor je niet zomaar vertrouwt, zijn ontvangsten hoe je de audittrail eerlijk maakt.

Het belangrijkste inzicht: ontvangsten bewijzen wie wat, wanneer zei. Ze bewijzen niet dat wat werd gezegd waar of juist was. Houd dat onderscheid goed in gedachten. Het is het verschil tussen een eerlijk herkomstsysteem en een misleidend systeem.

Productie-checklist

Wanneer je klaar bent om van deze les over te stappen op het inzetten van agenten met ondertekende ontvangsten in een echte omgeving:

Meer vragen over het beveiligen van AI-agenten?

Word lid van de Microsoft Foundry Discord om andere leerlingen te ontmoeten, deel te nemen aan spreekuren, en antwoorden te krijgen op je AI-agent vragen.

Verder dan deze les

Deze les behandelt het ondertekenen van een enkele ontvangst en hash-geketende reeksen. Dezelfde primitieve technieken vormen de basis voor verschillende meer geavanceerde patronen die je kunt tegenkomen naarmate je governance-positie volwassen wordt:

Aanvullende bronnen

Vorige les

Lokale AI-agenten maken


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.