Se lektionsvideoen: Sikring af AI-agenter med kryptografiske kvitteringer
(Lektionsvideo og miniaturebillede tilføjes af Microsoft indholdsteam efter sammenfletning, i overensstemmelse med lektion 14 / 15 mønsteret.)
Denne lektion vil dække:
Når du har gennemført denne lektion, vil du vide, hvordan du:
Forestil dig, at du har deployeret en AI-agent for Contoso Travel. Agenten læser kundeforespørgsler, kalder et fly-API for at finde muligheder og booker pladser på kundens vegne. I det sidste kvartal behandlede agenten 50.000 reservationer.
I dag ankommer en revisor. De stiller et simpelt spørgsmål: “Vis mig, hvad din agent gjorde.”
Du overdrager dine logfiler. Revisoren ser på dem og stiller det sværere spørgsmål: “Hvordan ved jeg, at disse logs ikke er blevet redigeret?”
Dette er audit-trail-problemet. De fleste agentdeployeringer i dag baserer sig på:
Ingen af disse kan besvare revisorens spørgsmål uden, at revisoren skal stole på nogen (dig, din cloud-udbyder, din databaseleverandør). Til intern brug er den tillid ofte acceptabel. For regulerede arbejdsbelastninger (finans, sundhed, alt under EU AI-loven) er det ikke.
Kryptografiske kvitteringer løser dette ved at gøre hver agenthandling uafhængigt verificerbar. Revisoren behøver ikke at stole på dig. De behøver kun din offentlige nøgle og kvitteringen selv.
En kvittering er et JSON-objekt, der registrerer, hvad en agent gjorde, underskrevet med en digital signatur.
flowchart LR
A[Agent påkalder et værktøj] --> B[Opbyg kvitteringspayload]
B --> C[Kanoniser JSON RFC 8785]
C --> E[Ed25519 signer kanoniske bytes]
E --> F[Kvittering med signatur]
F --> G[Revisor verificerer offline]
G --> H{Er signaturen gyldig?}
H -- yes --> I[Manipulationssikker bevis]
H -- no --> J[Kvittering afvist]
En minimal kvittering ser sådan ud:
{
"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..."
}
}
Tre egenskaber gør arbejdet:
Signaturen. Kvitteringen underskrives af agentens gateway med en Ed25519 privat nøgle. Enhver med den tilsvarende offentlige nøgle kan verificere signaturen offline. Manipulation af et hvilket som helst felt ugyldiggør signaturen.
Kanonisk kodning. Før underskrift serialiseres kvitteringen med JSON Canonicalization Scheme (JCS, RFC 8785). Dette sikrer, at to implementeringer, der producerer samme logiske kvittering, også producerer byte-identisk output. Uden kanonisk kodning ville forskellige JSON-serialisatorer producere forskellige signaturer for samme indhold.
Hash-kædning. Feltet previous_receipt_hash forbinder hver kvittering til den foregående. Fjernelse eller omrokering af en kvittering bryder alle kvitteringer efter den. Manipulation bliver synlig på kæde-niveau, selv hvis individuelle signaturer bliver omgået.
Sammen giver disse egenskaber tre garantier:
Du behøver ikke et særligt bibliotek for at producere en kvittering. De kryptografiske primitive findes bredt, og logikken er kun nogle få dusin linjer Python.
De praktiske øvelser i code_samples/18-signed-receipts.ipynb gennemgår hele flowet. Her er en opsummering:
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize # RFC 8785 kanonisk 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()}"
# Generer eller indlæs en signeringsnøgle (i produktion, gem i en nøgleboks)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
# Byg kvitteringsindholdet (ingen signatur endnu)
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,
}
# Kannoniser og signer JCS-bytes direkte. PureEdDSA hasher internt.
canonical_bytes = canonicalize(payload)
signature_bytes = signing_key.sign(canonical_bytes).signature
# Vedhæft et struktureret signaturobjekt.
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
Det er hele underskrifts-pipelinen. Øvelserne i notebooken gennemgår hvert trin.
Verifikation er den omvendte operation:
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:
# Signaturen er et struktureret objekt: {"alg", "sig", "public_key"}.
sig_obj = receipt.get("signature")
if not sig_obj or sig_obj.get("alg") != "EdDSA":
return False
# Genskab den nyttelast, der faktisk blev underskrevet (alt undtagen signaturen).
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
Denne funktion tager en kvittering og returnerer True, hvis signaturen er gyldig, ellers False. Ingen netværkskald, ingen servicedependency, ingen tillid nødvendig til tredjepart.
For at se manipulation opdages i praksis, gennemgår notebooken:
tool_args_hash.Dette er den praktiske demonstration af, at kvitteringer er manipulationssikre: enhver ændring, uanset hvor lille, bryder signaturen.
En enkelt underskrevet kvittering beskytter en handling. En kæde af kvitteringer beskytter en sekvens.
flowchart LR
R0[Kvittering 0<br/>genese] --> R1[Kvittering 1]
R1 --> R2[Kvittering 2]
R2 --> R3[Kvittering 3]
R1 -. previous_receipt_hash .-> R0
R2 -. previous_receipt_hash .-> R1
R3 -. previous_receipt_hash .-> R2
Hver kvittering registrerer hash-værdien af den foregående kvittering. For at fjerne kvittering 2 uden at blive opdaget, skal en angriber enten:
previous_receipt_hash felt (bryder kvittering 3’s signatur), ELLERHvis den private nøgle er i en hardware-nøgleboks, og du offentliggør den offentlige nøgle med hver kvittering, er ingen af angrebene mulige uden at blive opdaget.
Notebooken gennemgår:
previous_receipt_hash matcher den faktiske hash af den forrige kvittering.Sådan producerer du et revisionsspor, som en ekstern revisor kan verificere uden at skulle have tillid til dig.
Dette er det vigtigste afsnit i denne lektion. Kvitteringer er kraftfulde, men deres kraft er begrænset.
Kvitteringer beviser tre ting:
Kvitteringer BEVISEr IKKE:
policy_id rent faktisk blev evalueret, eller at den ville have tilladt handlingen ved kontrol. Kvitteringen registrerer, hvad der blev hævdet, ikke hvad der blev håndhævet.Denne grænse er vigtig af to grunde:
En almindelig fejl er at antage, at “vi har kvitteringer” betyder “vi er styret.” Det gør det ikke. Kvitteringer er en grundsten. Styring er systemet, du bygger ovenpå.
Punkt 3 ovenfor fortjener sit eget afsnit: en handlingskvittering siger “denne nøgle underskrev dette indhold,” aldrig “et menneske godkendte dette.” For højrisiaktioner (refusioner, sletninger, overførsler) kræver styringsrammer i stigende grad netop denne manglende erklæring, og den kan produceres med de samme primitive, du allerede byggede i denne lektion.
Den efterfølgende notebook code_samples/human-authorization-receipts.ipynb tilføjer en anden kvitteringstype, human.approval.v1, i samme kuvertform som lektionens kvitteringer (en typet payload underskrevet af Ed25519 over sine kanoniske JCS-bytes, med signature-objektet uden for de underskrevne bytes). En navngiven godkender underskriver hele den kanoniske handling og dens digest før eksekvering; agentens handlingskvittering indeholder samme handlingsdigest og en parent_approval_ref, godkendelsens receipt_hash, samme konvention som previous_receipt_hash i kæden du byggede ovenfor. En verify_chain verificerer begge artefakter under separate fastlåste nøgleregistre (godkender-nøgler vs. agent-nøgler), så kodevejen deles, men myndighederne aldrig gør.
Den egenskab, det giver, formuleret nøje: mennesket godkendte denne præcise handling, og agenten udførte netop den godkendte handling. Notebookens afvisnings-fixtures er det, der gør egenskaben reel snarere end påstået:
Hver fejl afviser med en særskilt grund, så en revisor, der læser afvisningen, kan afgøre, om myndigheden er udløbet eller om den eksekverede handling er ændret. Reglen notebooken lærer: en underskrevet godkendelse er ikke myndighed i sig selv. Myndighed eksisterer kun, hvis begge kvitteringer binder til samme kanoniske handling på eksekveringstidspunktet. Menneske-godkendelseskvitteringen er en pædagogisk sammensætning defineret i denne lektion, ikke en kvitteringstype defineret af draft-farley-acta-signed-receipts.
Python-koden i denne lektion er bevidst minimal, så du kan læse hver linje og forstå præcis, hvad der sker. I produktion har du to muligheder:
Byg direkte på de kryptografiske primitive. De 50 linjer, du så ovenfor, er tilstrækkelige til mange brugstilfælde. PyNaCl (Ed25519) og jcs-pakken (kanonisk JSON) er velvedligeholdte og reviderede biblioteker.
Brug et produktionsklar kvitteringsbibliotek. Flere open-source projekter implementerer samme mønster med ekstra funktioner (nøgle-rotation, batch-verifikation, JWK Set-distribution, integration med policy-engine):
draft-farley-acta-signed-receipts, revision 02). Lektionens flade uddannelseskvittering adskiller sig fra draftets {payload, signature} kuvert og præsenteres ikke som en konform implementering. Draftet udgiver en fælles konformitetstestpakke (agent-governance-testvectors) for implementeringer, der målretter dets wire-format.protect-mcp (npm) og @veritasacta/verify (npm) pakkerne leverer en Node-baseret implementering af kvitteringssignering og offline verifikation, beregnet til at omslutte enhver MCP-server med et manipulationssikkert revisionsspor, inklusive en held-for-co-sign flow, hvor en pausere handling udsteder en godkendelseskvittering bundet til handlingsdigest (WebAuthn-backede i desktop-flowet), samme godkendelses-kvitteringsmønster som den menneskeautorisation-notebook, der er omtalt ovenfor.pip install nobulex) leverer det samme Ed25519 + JCS underskriftsmønster i Python med LangChain og CrewAI integrationer, inklusiv offentliggjorte krydsvaliderings-testvektorer og en overholdelseskortlægning bidraget via OWASP PR #2210.Valget mellem at bygge selv og bruge et bibliotek spejler beslutningen mellem at skrive dit eget JWT-bibliotek og bruge et testet et: begge er rimelige; biblioteket sparer tid og reducerer revisionsfladen; den fra-grunden-tilgang tvinger dig til at forstå hver primitiv. Denne lektion underviser i fra-grund-metoden, så du har fundamentet for begge valg.
Test din forståelse inden du går videre til øvelsen.
1. En kvittering er underskrevet med agentens private Ed25519-nøgle. Revisor har kun den offentlige nøgle. Kan revisor verificere kvitteringen offline?
2. En angriber ændrer kvitteringens policy_id-felt for at påstå, at det var underlagt en mere lempelig politik. Signaturen var over den oprindelige payload. Hvad sker der under verifikationen?
3. Hvorfor indeholder kvitteringen en tool_args_hash og result_hash i stedet for de rå argumenter og resultat?
4. Feltet previous_receipt_hash linker hver kvittering til dens forgænger. Hvis en angriber stille sletter én kvittering midt i en kæde, hvad bliver så ugyldigt?
5. En kvittering verificeres rent. Beviser det, at agentens handling var korrekt, forsvarlig eller i overensstemmelse med politikken?
Åbn code_samples/18-signed-receipts.ipynb og fuldfør alle fire sektioner:
Udvidelsesudfordring 1: udvid kvitteringsskemaet med et yderligere felt efter eget valg (for eksempel en anmodnings-ID til sporing), opdater den kanoniske signeringslogik til at inkludere det, og bekræft at kvitteringen stadig kan gå gennem verificering. Ændr derefter feltet efter signering, og bekræft, at verificering mislykkes. Dette tvinger dig til at forstå, hvordan hver byte i den kanoniske kodning bidrager til signaturen.
Udvidelsesudfordring 2: SHA-256-hash to af dine kvitteringer sammen (sammenkæd deres kanoniske bytes i en deterministisk rækkefølge) og indlej den resulterende digest som et nyt felt på en tredje kvittering inden signering. Verificer at alle tre kvitteringer stadig kan gå igennem verificering. Du har lige bygget et inklusionsbevis på ét trin: enhver, der holder den tredje kvittering, kan bevise at de to første eksisterede på tidspunktet for den sene signering uden at skulle afsløre deres indhold. Dette er mønsteret, som selective-disclosure kvitteringer bruger i stor skala (Merkle-forpligtelser, RFC 6962).
Kryptografiske kvitteringer giver AI-agenter et revisionsspor, der er:
De er ikke en erstatning for inputvalidering, håndhævelse af politik, eller identitetsinfrastruktur. De er et fundament for disse lag. Når du implementerer agenter i regulerede arbejdsbelastninger, workflows mellem flere organisationer, eller enhver kontekst hvor en fremtidig revisor ikke kan antages at stole på dig, er kvitteringer hvordan du gør revisionssporet ærligt.
Det vigtigste at tage med: kvitteringer beviser, hvem der sagde hvad, hvornår. De beviser ikke, at det sagte var sandt eller rigtigt. Hold denne sondring stramt. Det er forskellen mellem et ærligt oprindelsessystem og et misvisende.
Når du er klar til at gå videre fra denne lektion til at implementere kvitterings-signerende agenter i et rigtigt miljø:
https://your-org.example.com/.well-known/agent-keys.json.Deltag i Microsoft Foundry Discord for at mødes med andre lærende, deltage i kontortimer, og få svar på dine AI Agent-spørgsmål.
Denne lektion dækker enkelt-kvitteringssignering og hash-kædede sekvenser. De samme primitive bygger block for flere mere avancerede mønstre, du kan møde, efterhånden som din governance-tilgang modnes:
authorization_*) og post-eksekvering (result_*) halvdele med uafhængige signaturer, nyttigt når autorisationsbeslutningen og det observerede resultat produceres af forskellige aktører eller på forskellige tidspunkter. Dette bygger oven på kvitteringsformatet undervist i denne lektion.result_hash. Virkelige nyttelaster er ofte rigere end et enkelt værktøjskald: forudgående beslutningsgrundlag (modelprediktion, overvejede muligheder, bevis og dets fuldstændighed, risikoposition, ansvarskæde, gate-udfald) kan alle bo i nyttelasten, forseglet af en enkelt kvittering. Dette holder kvitteringsformatet minimalt samtidig med at nytteladningsskemaer kan udvikle sig domæne-for-domæne.signature.alg kan bære ML-DSA-65 (NIST’s post-kvante signaturstandard), når du har brug for at migrere. Planlæg en overgangsperiode hvor kvitteringer er dobbeltsigneret.Oprettelse af lokale AI-agenter
Ansvarsfraskrivelse: Dette dokument er blevet oversat ved hjælp af AI-oversættelsestjenesten Co-op Translator. Selvom vi bestræber os på nøjagtighed, skal du være opmærksom på, at automatiserede oversættelser kan indeholde fejl eller unøjagtigheder. Det originale dokument på dets oprindelige sprog bør betragtes som den autoritative kilde. For kritisk information anbefales professionel menneskelig oversættelse. Vi påtager os intet ansvar for misforståelser eller fejltolkninger, der opstår som følge af brugen af denne oversættelse.