Obejrzyj wideo z lekcji: Zabezpieczanie agentów AI za pomocą kryptograficznych potwierdzeń
(Wideo z lekcji i miniaturka zostaną dodane przez zespół Microsoft ds. treści po scaleniu, zgodnie ze wzorcem lekcji 14 / 15.)
W tej lekcji omówimy:
Po ukończeniu tej lekcji będziesz potrafił:
Wyobraź sobie, że wdrożyłeś agenta AI dla Contoso Travel. Agent odczytuje zapytania klientów, wywołuje API lotów, by znaleźć opcje, i rezerwuje miejsca w ich imieniu. W ostatnim kwartale agent przetworzył 50 000 rezerwacji.
Dzisiaj przybywa audytor. Zadaje proste pytanie: „Pokaż mi, co zrobił twój agent.”
Przekazujesz mu swoje pliki dziennika. Audytor je przegląda i zadaje trudniejsze pytanie: „Skąd mam wiedzieć, że te dzienniki nie zostały zmienione?”
To jest problem ścieżki audytu. Większość wdrożeń agentów obecnie opiera się na:
Żaden z nich nie potrafi odpowiedzieć na pytanie audytora bez wymogu zaufania do kogoś (ciebie, dostawcy chmury, producenta bazy danych). Dla użytku wewnętrznego to zaufanie zwykle jest akceptowalne. Dla obciążonych regulacjami środowisk (finanse, opieka zdrowotna, cokolwiek objęte unijnym rozporządzeniem AI) – nie jest.
Kryptograficzne potwierdzenia rozwiązują to, umożliwiając niezależną weryfikację każdego działania agenta. Audytor nie musi ci ufać. Wystarczy mu twój klucz publiczny i samo potwierdzenie.
Potwierdzenie to obiekt JSON, który zapisuje, co agent zrobił, podpisany cyfrowo.
flowchart LR
A[Agent wywołuje narzędzie] --> B[Buduj ładunek potwierdzenia]
B --> C[Kanoniczuj JSON RFC 8785]
C --> D[Hash SHA-256]
D --> E[Podpis Ed25519]
E --> F[Potwierdzenie z podpisem]
F --> G[Audytor weryfikuje offline]
G --> H{Podpis ważny?}
H -- tak --> I[Dowód wykazujący ingerencję]
H -- nie --> J[Potwierdzenie odrzucone]
Minimalne potwierdzenie wygląda tak:
{
"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..."
}
}
Trzy właściwości wykonują tu pracę:
Podpis. Potwierdzenie jest podpisane przez bramkę agenta kluczem prywatnym Ed25519. Każdy, kto ma odpowiadający klucz publiczny, może zweryfikować podpis offline. Manipulacja którymkolwiek polem unieważnia podpis.
Kanoniczne kodowanie. Przed podpisaniem potwierdzenie jest serializowane zgodnie ze schematem kanonicznego JSON (JCS, RFC 8785). Zapewnia to, że dwie implementacje produkujące logicznie ten sam dokument dają identyczny wynik bajtowy. Bez kanoniczności różne serializatory JSON dawałyby różne podpisy tego samego ładunku.
Łańcuchowanie haszami. Pole previous_receipt_hash łączy każde potwierdzenie z poprzednim. Usunięcie lub zmiana kolejności potwierdzenia niszczy wszystkie kolejne. Manipulacje stają się widoczne na poziomie łańcucha, nawet jeśli pojedyncze podpisy zostałyby ominięte.
Wspólnie te właściwości dają trzy gwarancje:
Nie potrzebujesz specjalnej biblioteki, by wygenerować potwierdzenie. Prymitywy kryptograficzne są powszechnie dostępne, a logika to kilkadziesiąt linijek Pythona.
Ćwiczenia praktyczne w code_samples/18-signed-receipts.ipynb przeprowadzają przez cały proces. Wersja skrócona:
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize # Kanoniczny JSON zgodny z RFC 8785
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()}"
# Wygeneruj lub załaduj klucz podpisujący (w produkcji przechowuj w sejfie na klucze)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
# Zbuduj ładunek potwierdzenia (jeszcze bez podpisu)
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,
}
# Kanonizuj, haszuj, podpisz.
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
signature_bytes = signing_key.sign(message_hash).signature
# Dołącz strukturalny obiekt podpisu.
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
To cały pipeline podpisywania. Ćwiczenia w notatniku przeprowadzą cię przez każdy krok.
Weryfikacja to operacja odwrotna:
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:
# Podpis jest obiektem strukturalnym: {"alg", "sig", "public_key"}.
sig_obj = receipt.get("signature")
if not sig_obj or sig_obj.get("alg") != "EdDSA":
return False
# Odtwórz ładunek, który został faktycznie podpisany (wszystko oprócz podpisu).
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
Ta funkcja przyjmuje potwierdzenie i zwraca True, jeśli podpis jest ważny, False w przeciwnym wypadku. Bez wywołań sieciowych, bez zależności od serwisów, bez konieczności zaufania stronom trzecim.
Aby zobaczyć działanie wykrywania manipulacji, notatnik pokazuje:
tool_args_hash.To praktyczny dowód na to, że potwierdzenia są odporne na manipulacje: każda zmiana, choćby najdrobniejsza, łamie podpis.
Pojedyncze podpisane potwierdzenie chroni jedno działanie. Łańcuch potwierdzeń chroni sekwencję działań.
flowchart LR
R0[Pokwitowanie 0<br/>geneza] --> R1[Pokwitowanie 1]
R1 --> R2[Pokwitowanie 2]
R2 --> R3[Pokwitowanie 3]
R1 -. previous_receipt_hash .-> R0
R2 -. previous_receipt_hash .-> R1
R3 -. previous_receipt_hash .-> R2
Każde potwierdzenie zapisuje hash poprzedniego potwierdzenia. Aby cicho usunąć potwierdzenie nr 2, atakujący musiałby albo:
previous_receipt_hash potwierdzenia nr 3 (co łamie podpis potwierdzenia nr 3), LUBJeśli klucz prywatny jest w sprzętowym sejfie kluczy, a ty publikujesz klucz publiczny przy każdym potwierdzeniu, żadna z tych opcji nie jest wykonalna bez wykrycia.
Notatnik przeprowadza przez:
previous_receipt_hash odpowiada faktycznemu hashowi poprzedniego potwierdzenia.Tak produkujesz ścieżkę audytu, którą zewnętrzny audytor może zweryfikować bez konieczności zaufania tobie.
To najważniejsza sekcja tej lekcji. Potwierdzenia są potężne, ale ich moc jest ograniczona.
Potwierdzenia udowadniają trzy rzeczy:
Potwierdzenia NIE udowadniają:
policy_id była faktycznie oceniana albo że dopuściłaby tę akcję weryfikowaną przez system. Potwierdzenie zapisuje to, co zgłoszono, nie to, co wymuszono.Ta granica jest ważna z dwóch powodów:
Częstym błędem jest zakładanie, że „mamy potwierdzenia” oznacza „jesteśmy zarządzani.” Tak nie jest. Potwierdzenia to podstawa. Zarządzanie to system, który budujesz na tej podstawie.
Kod Pythona w tej lekcji jest celowo minimalny, abyś mógł przeczytać każdą linię i dokładnie zrozumieć, co się dzieje. W produkcji masz dwie opcje:
Budować bezpośrednio na prymitywach kryptograficznych. 50 linijek, które widziałeś, wystarczy dla wielu zastosowań. PyNaCl (Ed25519) oraz pakiet jcs (kanoniczny JSON) to dobrze utrzymane i audytowane biblioteki.
Używać produkcyjnej biblioteki potwierdzeń. Kilka projektów open source implementuje ten sam wzorzec z dodatkowymi funkcjami (rotacja kluczy, weryfikacja wsadowa, dystrybucja zestawu JWK, integracja z silnikami polityk):
draft-farley-acta-signed-receipts) obecnie w procesie standaryzacji.protect-mcp (npm) i @veritasacta/verify (npm) oferują implementację Node do podpisywania potwierdzeń i weryfikacji offline, przeznaczoną do opakowywania dowolnego serwera MCP w ścieżkę audytu odporną na manipulacje.pip install nobulex) dostarcza ten sam wzorzec Ed25519 + JCS w Pythonie z integracjami LangChain i CrewAI, w tym opublikowane wektory testowe do walidacji krzyżowej oraz mapowanie zgodności wniesione przez OWASP PR #2210.Decyzja między napisaniem własnego rozwiązania a użyciem biblioteki przypomina wybór między własną biblioteką JWT a przetestowaną: obie są rozsądne; biblioteka oszczędza czas i zmniejsza powierzchnię audytu; własne rozwiązanie wymusza zrozumienie każdego prymitywu. Ta lekcja uczy drogi od podstaw, abyś miał solidną bazę dla każdej opcji.
Sprawdź swoją wiedzę przed przejściem do ćwiczenia praktycznego.
1. Potwierdzenie jest podpisane kluczem prywatnym Ed25519 agenta. Audytor ma tylko klucz publiczny. Czy audytor może zweryfikować potwierdzenie offline?
2. Atakujący zmienia pole policy_id potwierdzenia, twierdząc, że było zarządzane bardziej permisywną polityką. Podpis był z oryginalnym ładunkiem. Co się stanie podczas weryfikacji?
3. Dlaczego potwierdzenie zawiera tool_args_hash i result_hash zamiast surowych argumentów i wyniku?
4. Pole previous_receipt_hash łączy każde potwierdzenie z poprzednikiem. Co staje się nieprawidłowe, jeśli atakujący cicho usunie jedno potwierdzenie ze środka łańcucha?
5. Potwierdzenie weryfikuje się poprawnie. Czy to dowód na to, że działanie agenta było poprawne, prawidłowe i zgodne z polityką?
Otwórz code_samples/18-signed-receipts.ipynb i wykonaj wszystkie cztery sekcje:
Wyzwanie dodatkowe 2: wykonaj funkcję skrótu SHA-256 dwóch swoich pokwitowań razem (połącz ich kanoniczne bajty w deterministycznej kolejności) i osadź wynikowy skrót jako nowe pole w trzecim pokwitowaniu przed jego podpisaniem. Zweryfikuj, że wszystkie trzy pokwitowania nadal przechodzą poprawnie weryfikację. Właśnie zbudowałeś dowód inkluzji w jednym kroku: każdy posiadający trzecie pokwitowanie może udowodnić, że dwa pierwsze istniały w czasie jego podpisania, bez konieczności ujawniania ich zawartości. Jest to wzorzec używany w pokwitowaniach selektywnego ujawniania na dużą skalę (zob. zobowiązania Merkle’a, RFC 6962).
Kryptograficzne pokwitowania dają agentom AI ślad audytowy, który jest:
Nie są one substytutem walidacji wejścia, egzekwowania polityki ani infrastruktury tożsamości. Są fundamentem dla tych warstw. Gdy wdrażasz agentów do obciążeń podlegających regulacjom, przepływów pracy wielu organizacji lub w każdym środowisku, gdzie przyszły audytor nie może zakładać, że ci ufa, pokwitowania są sposobem na uczciwy ślad audytowy.
Najważniejszy wniosek: pokwitowania udowadniają, kto co powiedział i kiedy. Nie udowadniają, że to, co powiedziano, było prawdziwe lub poprawne. Trzymaj tę różnicę mocno. To różnica między uczciwym systemem pochodzenia a wprowadzającym w błąd.
Gdy będziesz gotowy przejść od tej lekcji do wdrożenia agentów podpisujących pokwitowania w środowisku produkcyjnym:
https://your-org.example.com/.well-known/agent-keys.json.Dołącz do Microsoft Foundry Discord, aby spotkać innych uczących się, uczestniczyć w godzinach konsultacji i uzyskać odpowiedzi na pytania dotyczące agentów AI.
Ta lekcja obejmuje podpisywanie pojedynczych pokwitowań oraz sekwencje połączone skrótem. Te same prymitywy składają się na kilka bardziej zaawansowanych wzorców, które możesz napotkać wraz z rozwojem swojej postawy zarządzania:
authorization_*) i powykonawczą (result_*) z niezależnymi podpisami, przydatne, gdy decyzję autoryzacyjną i obserwowany rezultat tworzą różni aktorzy lub w różnym czasie. Składa się to addytywnie na format pokwitowania omawiany w tej lekcji.result_hash. Rzeczywiste ładunki są często bogatsze niż wynik pojedynczego wywołania narzędzia: rozumowanie przed decyzją (prognoza modelu, rozważane opcje, dowody i ich kompletność, ryzyko, łańcuch odpowiedzialności, wynik bramki) może znajdować się w ładunku zapieczętowanym jednym pokwitowaniem. To pozwala zachować minimalny format pokwitowania, umożliwiając rozwój schematów ładunku dla konkretnej dziedziny.signature.alg może zawierać ML-DSA-65 (standard podpisu postkwantowego NIST) gdy nadejdzie potrzeba migracji. Zaplanuj okres przejściowy, w którym pokwitowania będą podwójnie podpisane.Budowanie agentów do użycia komputera (CUA)
(Do ustalenia przez opiekunów programu nauczania)
Zastrzeżenie: Niniejszy dokument został przetłumaczony za pomocą usługi tłumaczenia AI Co-op Translator. Choć dążymy do dokładności, prosimy pamiętać, że automatyczne tłumaczenia mogą zawierać błędy lub niedokładności. Oryginalny dokument w jego języku źródłowym należy uznawać za autorytatywne źródło. W przypadku informacji krytycznych zalecane jest skorzystanie z profesjonalnego tłumaczenia wykonanego przez człowieka. Nie ponosimy odpowiedzialności za jakiekolwiek nieporozumienia lub błędne interpretacje wynikające z użycia tego tłumaczenia.