Titta på lektionens video: Säkerställ AI-agenter med kryptografiska kvittenser
(Lektionsvideo och miniatyrbild kommer att läggas till av Microsofts innehållsteam efter sammanslagning, i enlighet med mönstret för lektion 14 / 15.)
Den här lektionen kommer att täcka:
Efter att ha slutfört denna lektion kommer du att kunna:
Föreställ dig att du har driftsatt en AI-agent för Contoso Travel. Agenten läser kundförfrågningar, anropar en flygAPI för att leta upp alternativ och bokar platser för kundens räkning. Förra kvartalet hanterade agenten 50 000 bokningar.
Idag kommer en revisor. De ställer en enkel fråga: “Visa mig vad din agent gjorde.”
Du lämnar över dina loggfiler. Revisorn tittar på dem och ställer den svårare frågan: “Hur vet jag att dessa loggar inte har ändrats?”
Detta är problemet med revisionsspår. De flesta agentdriftsättningar idag förlitar sig på:
Ingen av dessa kan svara revisorns fråga utan att revisorn måste lita på någon (dig, din molnleverantör, din databaskund). För internt bruk är det ofta acceptabelt. För reglerade arbetsbelastningar (finans, vård, allt som omfattas av EU:s AI-förordning) är det inte det.
Kryptografiska kvittenser löser detta genom att göra varje agenthandling oberoende verifierbar. Revisorn behöver inte lita på dig. De behöver bara din offentliga nyckel och kvittensen.
En kvittens är ett JSON-objekt som registrerar vad en agent gjorde, signerat med en digital signatur.
flowchart LR
A[Agenten anropar ett verktyg] --> B[Bygg kvitto-payload]
B --> C[Kanonisering av JSON RFC 8785]
C --> D[SHA-256 hash]
D --> E[Ed25519 signera]
E --> F[Kvitto med signatur]
F --> G[Revisor verifierar offline]
G --> H{Signaturen giltig?}
H -- yes --> I[Manipuleringssäker bevisning]
H -- no --> J[Kvitto avvisat]
En minimal kvittens ser ut så här:
{
"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 egenskaper gör jobbet:
Signaturen. Kvittensen signeras av agentens gateway med en privat Ed25519-nyckel. Vem som helst med motsvarande offentlig nyckel kan verifiera signaturen offline. Manipulation av något fält ogiltigförklarar signaturen.
Kanonisk kodning. Innan signering serialiseras kvittensen med JSON Canonicalization Scheme (JCS, RFC 8785). Detta säkerställer att två implementationer som producerar samma logiska kvittens ger byte-identisk output. Utan kanonisering skulle olika JSON-serialiserare ge olika signaturer för samma innehåll.
Hash-kedjning. Fältet previous_receipt_hash länkar varje kvittens till den före. Att ta bort eller omordna en kvittens bryter varje kvittens efter den. Manipulation blir synlig på kedjenivå även om individuella signaturer kringgås.
Tillsammans ger dessa egenskaper tre garantier:
Du behöver inget speciellt bibliotek för att producera en kvittens. De kryptografiska primitiva funktionerna är allmänt tillgängliga och logiken är några tiotals rader Python.
De praktiska övningarna i code_samples/18-signed-receipts.ipynb går igenom hela flödet. Sammandraget:
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()}"
# Generera eller ladda en signeringsnyckel (i produktion, lagra i en nyckelvalv)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
# Bygg kvittots nyttolast (ingen signatur än)
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,
}
# Kanonisera, hasha, signera.
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
signature_bytes = signing_key.sign(message_hash).signature
# Bifoga ett strukturerat signaturobjekt.
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
Det är hela signeringskedjan. Övningarna i notebooken går igenom varje steg.
Verifiering är den omvända operationen:
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 är ett strukturerat objekt: {"alg", "sig", "public_key"}.
sig_obj = receipt.get("signature")
if not sig_obj or sig_obj.get("alg") != "EdDSA":
return False
# Återskapa den information som faktiskt signerades (allt utom signaturen).
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
Denna funktion tar en kvittens och returnerar True om signaturen är giltig, False annars. Ingen nätverksanrop, inget tjänsteberoende, ingen tredjepartstro behövs.
För att se upptäckt av manipulation i praktiken går notebooken igenom:
tool_args_hash.Detta är den praktiska demonstrationen att kvittenser är manipulationssynliga: varje ändring, hur liten som helst, bryter signaturen.
En enda signerad kvittens skyddar en handling. En kedja av kvittenser skyddar en sekvens.
flowchart LR
R0[Kvitto 0<br/>ursprung] --> R1[Kvitto 1]
R1 --> R2[Kvitto 2]
R2 --> R3[Kvitto 3]
R1 -. previous_receipt_hash .-> R0
R2 -. previous_receipt_hash .-> R1
R3 -. previous_receipt_hash .-> R2
Varje kvittens registrerar hashvärdet av föregående kvittens. För att tyst ta bort kvittens 2 skulle en angripare behöva antingen:
previous_receipt_hash i kvittens 3 (bryter signaturen för kvittens 3), ELLEROm den privata nyckeln finns i ett hårdvarunyckelförråd och du publicerar den offentliga nyckeln med varje kvittens, är ingen av dessa attacker genomförbar utan upptäckt.
Notebooken går igenom:
previous_receipt_hash matchar den faktiska hashen av föregående kvittens.Så här producerar du ett revisionsspår som en extern revisor kan verifiera utan att behöva lita på dig.
Detta är lektionens viktigaste avsnitt. Kvittenser är kraftfulla men deras kraft är begränsad.
Kvittenser bevisar tre saker:
Kvittenser bevisar INTE:
policy_id faktiskt utvärderades, eller att den hade tillåtit denna handling om den kontrollerades. Kvittensen registrerar vad som påstås, inte vad som genomfördes.Denna gräns är viktig av två skäl:
Ett vanligt misstag är att anta att “vi har kvittenser” betyder “vi är styrda.” Det gör det inte. Kvittenser är en grund. Styrning är systemet du bygger ovanpå.
Punkt 3 ovan förtjänar ett eget avsnitt: en handlingskvittens säger “denna nyckel signerade detta innehåll,” aldrig “en människa auktoriserade detta.” För högriskhandlingar (återbetalningar, raderingar, banköverföringar) kräver styrningsramverk alltmer just det saknade uttalandet, och det kan produceras med samma primitiva funktioner som du redan byggt i denna lektion.
Den efterföljande notebooken code_samples/human-authorization-receipts.ipynb lägger till en andra typ av kvittens, human.approval.v1, i samma kuvertform som lektionens kvittenser (en typad payload signerad med Ed25519 över dess kanoniska SHA-256, med signature-objektet utanför de signerade byten). En namngiven godkännare signerar hela den kanoniska åtgärden och dess digest före utförande; agentens handlingskvittens bär samma åtgärdsdigest och en parent_approval_ref, receipt_hash för godkännandet, samma konvention som previous_receipt_hash i kedjan du byggde ovan. En verify_chain går igenom båda artefakter under separata fastlagda nyckelregister (godkännar-nycklar vs agent-nycklar), så kodvägen delas men myndigheterna gör det aldrig.
Egenskapen detta ger, noggrant uttryckt: människan godkände denna exakta handling, och agenten utförde exakt den godkända handlingen. Notebookens vägran-fixturer gör att egenskapen är verklig och inte bara påstående:
Varje fel nekas med en distinkt orsak, så en revisor som läser ett avslag kan avgöra om myndigheten blev föråldrad eller om den utförda handlingen ändrades. Reglen som notebooken lär ut: en signerad godkännande är inte myndighet i sig. Myndighet finns endast om båda kvittenserna fortfarande binder till samma kanoniska handling vid utförandet. Samtidig signeringsbanan i samma Internet-Draft som denna lektion följer (draft-farley-acta-signed-receipts) är standardspåret för detta mönster.
Pythons koden i denna lektion är avsiktligt minimal så att du kan läsa varje rad och förstå exakt vad som händer. I produktion har du två alternativ:
Bygg direkt på de kryptografiska primitiva. De 50 rader du såg ovan räcker för många användningsfall. PyNaCl (Ed25519) och paketet jcs (kanonisk JSON) är väl underhållna och granskade bibliotek.
Använd ett produktionsbibliotek för kvittenser. Flera öppen källkod-projekt implementerar samma mönster med extra funktioner (nyckelrotation, batch-verifiering, JWK Set-distribution, integration med policymotorer):
draft-farley-acta-signed-receipts, revision 02) som för närvarande är i standardiseringsprocessen, med en delad konformitetssvit (agent-governance-testvectors) som oberoende implementationer korsverifierar mot för byte-identisk kanonisk output.protect-mcp (npm) och @veritasacta/verify (npm) tillhandahåller en Node-baserad implementering av kvittenssignering och offlineverifiering, avsedd för att omsluta vilken MCP-server som helst med ett manipulationssynligt revisionsspår, inklusive en håll-ved-kosignering-flöde där en pausad åtgärd avger en godkännandekvittens bunden till åtgärdsdigesten (WebAuthn-stödd i desktop-flödet), samma godkännande-kvittensmönster som den mänskliga auktorisations-notebooken ovan.pip install nobulex) tillhandahåller samma Ed25519 + JCS-signaturmönster i Python med LangChain och CrewAI-integrationer, inklusive publicerade korsvalideringstestvektorer och en efterlevnadskarta bidragen via OWASP PR #2210.Valet mellan att bygga eget och att använda ett bibliotek speglar valet mellan att skriva ett eget JWT-bibliotek och att använda ett testat: båda är rimliga; biblioteket spar tid och minskar granskningsyta; från-scratch tillvägagångssättet tvingar dig att förstå varje primitiv. Denna lektion lär ut från-scratch-vägen så att du har grunden för båda valen.
Testa din förståelse innan du går vidare till praktikuppgiften.
1. En kvittens är signerad med agentens privata Ed25519-nyckel. Revisorn har endast den offentliga nyckeln. Kan revisorn verifiera kvittensen offline?
2. En angripare modifierar policy_id-fältet i en kvittens för att påstå att den styrdes av en mer tillåtande policy. Signaturen var över den ursprungliga payloaden. Vad händer vid verifiering?
3. Varför inkluderar kvittot en tool_args_hash och result_hash istället för de råa argumenten och resultatet?
4. Fältet previous_receipt_hash länkar varje kvitto till dess föregångare. Om en angripare tyst tar bort ett kvitto från mitten av en kedja, vad blir ogiltigt?
5. Ett kvitto verifieras utan problem. Bevisar det att agentens handling var korrekt, giltig eller i enlighet med policyn?
Öppna code_samples/18-signed-receipts.ipynb och slutför alla fyra sektioner:
Extra utmaning 1: Utöka kvittoschemat med ett ytterligare valfritt fält (till exempel en förfrågnings-ID för spårning), uppdatera den kanoniska signeringslogiken så att det inkluderas, och bekräfta att kvittot fortfarande kan verifieras fram och tillbaka. Ändra sedan fältet efter signering och bekräfta att verifieringen misslyckas. Detta tvingar dig att förstå hur varje byte av den kanoniska kodningen bidrar till signaturen.
Extra utmaning 2: SHA-256-hasha två av dina kvitton tillsammans (kombinera deras kanoniska byte i en deterministisk ordning) och bädda in den resulterande digesten som ett nytt fält på ett tredje kvitto innan du signerar det. Verifiera att alla tre kvitton fortfarande kan verifieras fram och tillbaka. Du har just byggt ett enstegs inklusionsbevis: vem som helst som har det tredje kvittot kan bevisa att de två första existerade vid tiden då det signerades, utan att behöva avslöja deras innehåll. Detta är mönstret som selektiva avslöjande-kvitton använder i stor skala (Merkle-åtaganden, RFC 6962).
Kryptografiska kvitton ger AI-agenter en revisionskedja som är:
De ersätter inte inmatningsvalidering, policyimplementering eller identitetsinfrastruktur. De är en grund för dessa lager. När du distribuerar agenter i reglerade arbetsflöden, multi-organisationsarbetsflöden eller miljöer där en framtida revisor inte kan förväntas lita på dig, är kvitton hur du gör revisionskedjan ärlig.
Den viktigaste lärdomen: kvitton bevisar vem som sa vad och när. De bevisar inte att det som sades var sant eller rätt. Håll detta tydligt. Det är skillnaden mellan ett ärligt härledningssystem och ett vilseledande.
När du är redo att gå vidare från denna lektion till att distribuera kvittosignerade agenter i verklig miljö:
https://your-org.example.com/.well-known/agent-keys.json.Gå med i Microsoft Foundry Discord för att träffa andra som lär sig, delta i office hours och få svar på dina frågor om AI-agenter.
Denna lektion täcker enkel kvittosignering och hash-kedjade sekvenser. Samma primitiva byggstenar utgör flera mer avancerade mönster du kan stöta på när din styrningsstrategi mognar:
authorization_*) och post-exekverings- (result_*) halvor med oberoende signaturer, användbart när auktorisationsbeslutet och det observerade resultatet produceras av olika aktörer eller vid olika tillfällen. Detta bygger additivt på kvittformatet som lärs i denna lektion.result_hash. Verkliga nyttolaster är ofta rikare än ett enda verktygsanropsresultat: för-beslutsresonemang (modellprediktion, övervägda alternativ, bevis och dess fullständighet, riskposition, ansvarskedja, grindresultat) kan alla finnas inuti nyttolasten, förseglade av ett enda kvitto. Detta håller kvittoformatet minimalt samtidigt som nyttolastscheman kan utvecklas domän-för-domän.signature.alg kan bära ML-DSA-65 (NIST:s post-kvantums signaturstandard) när du behöver migrera. Planera för en övergångsperiod där kvitton är dubbel-signerade.Ansvarsfriskrivning: Detta dokument har översatts med hjälp av AI-översättningstjänsten Co-op Translator. Även om vi strävar efter noggrannhet, var vänlig notera att automatiska översättningar kan innehålla fel eller brister. Det ursprungliga dokumentet på dess modersmål bör betraktas som den auktoritativa källan. För kritisk information rekommenderas professionell mänsklig översättning. Vi ansvarar inte för några missförstånd eller feltolkningar som uppstår till följd av användningen av denna översättning.