Перегляньте відеоурок: Захист агента ШІ за допомогою криптографічних квитанцій
(Відеоурок і мініатюру додасть команда Microsoft після злиття, відповідно до шаблону уроків 14 / 15.)
У цьому уроці буде розглянуто:
Після завершення цього уроку ви знатимете, як:
Уявіть, що ви розгорнули агента ШІ для Contoso Travel. Агент читає запити клієнтів, звертається до API авіарейсів для пошуку варіантів і бронює місця від імені клієнта. Минулого кварталу агент обробив 50 000 бронювань.
Сьогодні приходить аудитор. Він ставить просте питання: “Покажіть, що зробив ваш агент.”
Ви передаєте свої журнали. Аудитор переглядає їх і ставить складніше питання: “Як я можу бути впевненим, що ці журнали не редагувалися?”
Ось у чому проблема аудиту. Більшість розгортань агентів сьогодні покладаються на:
Жоден із цих варіантів не може відповісти аудитору без необхідності довіряти комусь (вам, вашому хмарному провайдеру, вашому постачальнику бази даних). Для внутрішнього використання така довіра зазвичай прийнятна. Для регульованих робіт (фінанси, охорона здоров’я, усе, що підпадає під Закон ЄС про ШІ) — ні.
Криптографічні квитанції розв’язують це, роблячи кожну дію агента незалежно перевірюваною. Аудитору не потрібно довіряти вам. Потрібні лише ваш відкритий ключ і сама квитанція.
Квитанція — це об’єкт JSON, який фіксує, що зробив агент, підписаний цифровим підписом.
flowchart LR
A[Агент викликає інструмент] --> B[Створення корисного навантаження квитанції]
B --> C[Канонізація JSON за RFC 8785]
C --> D[Хеш SHA-256]
D --> E[Підпис Ed25519]
E --> F[Квитанція з підписом]
F --> G[Аудитор перевіряє офлайн]
G --> H{Підпис дійсний?}
H -- yes --> I[Доказ, що виявляє підробку]
H -- no --> J[Квитанція відхилена]
Мінімальна квитанція виглядає так:
{
"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..."
}
}
Три властивості виконують основну роботу:
Підпис. Квитанція підписується шлюзом агента за допомогою приватного ключа Ed25519. Будь-хто із відповідним відкритим ключем може перевірити підпис офлайн. Будь-яке втручання в будь-яке поле робить підпис недійсним.
Канонічне кодування. Перед підписом квитанція серіалізується за схемою канонічного JSON (JCS, RFC 8785). Це гарантує, що дві реалізації, які створюють однакову логічну квитанцію, породять бінарно ідентичний вихідний файл. Без канонізації різні JSON-серіалізатори створили б різні підписи для того самого вмісту.
Хеш-ланцюжок. Поле previous_receipt_hash пов’язує кожну квитанцію з попередньою. Видалення або переміщення квитанції порушує всі подальші квитанції в ланцюжку. Втручання видно на рівні ланцюжка навіть якщо індивідуальні підписи обійдені.
Разом ці властивості дають три гарантії:
Для створення квитанції не потрібна спеціальна бібліотека. Криптографічні примітиви доступні повсюдно, а логіка займає лише кілька десятків рядків коду Python.
Практичні вправи в code_samples/18-signed-receipts.ipynb проходять через увесь процес. Основна версія:
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize # Канонічний JSON за 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()}"
# Згенерувати або завантажити ключ підпису (у виробництві зберігати у сховищі ключів)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
# Побудувати корисне навантаження квитанції (підпис поки відсутній)
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,
}
# Канонізувати, захешувати, підписати.
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
signature_bytes = signing_key.sign(message_hash).signature
# Додати структурований об'єкт підпису.
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
Це вся пайплайн підписування. У вправі в ноутбуці розглянуто кожен крок.
Перевірка — це зворотня операція:
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:
# Підпис є структурованим об'єктом: {"alg", "sig", "public_key"}.
sig_obj = receipt.get("signature")
if not sig_obj or sig_obj.get("alg") != "EdDSA":
return False
# Відтворіть корисне навантаження, яке фактично було підписано (все, крім підпису).
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
Ця функція приймає квитанцію і повертає True, якщо підпис дійсний, і False в іншому випадку. Жодних мережевих викликів, жодних залежностей від сервісів, жодної довіри до третіх сторін.
Щоб побачити в дії виявлення підробки, ноутбук проходить через:
tool_args_hash.Це практична демонстрація того, що квитанції захищені від підробки: будь-яка зміна, якою б маленькою вона не була, руйнує підпис.
Одна підписана квитанція захищає одну дію. Ланцюжок квитанцій захищає послідовність.
flowchart LR
R0[Квитанція 0<br/>генезис] --> R1[Квитанція 1]
R1 --> R2[Квитанція 2]
R2 --> R3[Квитанція 3]
R1 -. previous_receipt_hash .-> R0
R2 -. previous_receipt_hash .-> R1
R3 -. previous_receipt_hash .-> R2
Кожна квитанція містить хеш попередньої квитанції. Щоб непомітно видалити квитанцію 2, атакуючий мусив би:
previous_receipt_hash квитанції 3 (порушить підпис квитанції 3), АБОЯкщо приватний ключ зберігається в апаратному сховищі ключів і ви публікуєте відкритий ключ з кожною квитанцією, жоден із цих варіантів атаки неможливий без виявлення.
У ноутбуці проходять:
previous_receipt_hash кожної квитанції збігається з реальним хешем попередньої.Ось як створити журнал дій, який зовнішній аудитор може перевірити без довіри до вас.
Це найважливіший розділ цього уроку. Квитанції — потужний інструмент, але їх сила обмежена.
Квитанції доводять три речі:
Квитанції НЕ доводять:
Коректність: що дія агента була правильною. Квитанція може бути підписана для неправильної відповіді так само чисто, як і для правильної.
policy_id, була фактично перевірена, або що вона дозволила б цю дію, якби її перевіряли. Квитанція фіксує те, що було заявлено, а не те, що було виконано.Ця межа важлива з двох причин:
Загальна помилка — вважати, що “ми маємо квитанції” означає “ми управляємо процесом”. Це не так. Квитанції — це основа. Управління — це система, яку ви будуєте на її основі.
Пункт 3 вище заслуговує власного розділу: квитанція про дію каже “цей ключ підписав цей вміст,” ніколи не каже “людина авторизувала це.” Для ризикованих дій (повернення грошей, вилучення, перекази) рамки управління все більше вимагають саме цієї відсутньої заяви, і її можна створити за допомогою тих самих примітивів, які ви вже побудували в цьому уроці.
Наступна блокнот code_samples/human-authorization-receipts.ipynb додає другий тип квитанції, human.approval.v1, у тому ж форматі конверта, що квитанції уроку (типізований завантажувач підписаний Ed25519 над канонічним SHA-256, при цьому об’єкт signature поза підписаними байтами). Іменований схвалювач підписує повну канонічну дію і її дайджест до виконання; квитанція агента про дію несе той самий дайджест дії і parent_approval_ref, receipt_hash схвалення, так само, як previous_receipt_hash у ланцюжку, який ви побудували вище. Один verify_chain перевіряє обидва об’єкти під окремими керованими реєстрами ключів (ключі схвалювачів проти ключів агентів), тому код однаковий, але влада ніколи не перетинається.
Властивість, яку це дає, сформульована чітко: людина схвалила саме цю дію, а агент виконав саме ту схвалену дію. Відмови, задані в блокноті, роблять цю властивість реальною, а не заявленою:
Кожна відмова має окрему причину, щоб аудитор міг зрозуміти, чи втратила влада чинність, чи змінилася виконана дія. Правило, яке викладає блокнот: підписане схвалення саме по собі не є владою. Влада існує лише якщо обидві квитанції все ще пов’язані з тією ж канонічною дією на момент виконання. Спільний шлях співпідпису в тому самому Internet-Draft, на якому базується цей урок (draft-farley-acta-signed-receipts), є типовою формою цього паттерна.
Python-код у цьому уроці навмисно мінімальний, щоб ви могли прочитати кожен рядок і точно зрозуміти, що відбувається. Упродакшені ви маєте два варіанти:
Створити безпосередньо на криптографічних примітивах. 50 рядків, які ви бачили вище, достатньо для багатьох випадків. PyNaCl (Ed25519) і пакет jcs (канонічний JSON) — це добре підтримувані та перевірені бібліотеки.
Використати бібліотеку для роботи з квитанціями у продакшені. Кілька opensource-проєктів реалізують той самий паттерн із додатковими функціями (обертання ключів, пакетна перевірка, розповсюдження JWK-наборів, інтеграція з політичними движками):
draft-farley-acta-signed-receipts, ревізія 02), що наразі проходить стандартизацію, з набором тестів для відповідності (agent-governance-testvectors), які незалежні реалізації перевіряють, щоб отримати біт-в-біт канонічний результат.protect-mcp (npm) і @veritasacta/verify (npm) надають Node-реалізацію підписання квитанцій та офлайн-перевірку, призначені для обгортання будь-якого MCP-сервера з аудиторським слідом, захищеним від підробок, включно з режимом зберігання для співпідпису, де призупинена дія породжує квитанцію схвалення, пов’язану з дайджестом дії (підтримка WebAuthn у десктопному потоці), такий самий паттерн схвалення, як у блокноті для авторизації людиною вище.pip install nobulex) надає той самий паттерн підписання Ed25519 + JCS у Python з інтеграціями LangChain і CrewAI, включно з опублікованими тест-векторами валідації та мапінгом відповідності, запропонованим через OWASP PR #2210.Вибір між створенням власного рішення та використанням бібліотеки схожий на вибір між створенням власної JWT-бібліотеки і використанням перевіреної: обидва варіанти розумні; бібліотека економить час і зменшує площу аудиту; підхід з нуля змушує вас розуміти кожен примітив. Цей урок навчає підходу з нуля, щоб ви мали основу для будь-якого вибору.
Перевірте розуміння перед переходом до практичного завдання.
1. Квитанція підписана приватним Ed25519 ключем агента. Аудитор має лише публічний ключ. Чи може аудитор перевірити квитанцію офлайн?
2. Зловмисник змінює поле policy_id квитанції, щоб стверджувати, що вона регулювалася більш ліберальною політикою. Підпис накладено на оригінальне завантаження. Що відбувається під час перевірки?
3. Чому квитанція містить tool_args_hash і result_hash, а не сирі аргументи та результат?
4. Поле previous_receipt_hash пов’язує кожну квитанцію з попередником. Якщо зловмисник тихо видаляє одну квитанцію з середини ланцюжка, що стає недійсним?
5. Квитанція проходить перевірку. Чи доводить це, що дії агента були правильними, правильними або відповідали політиці?
Відкрийте code_samples/18-signed-receipts.ipynb і виконайте всі чотири розділи:
Додаткове завдання 1: розширте схему квитанції додатковим полем на ваш вибір (наприклад, ID запиту для трасування), оновіть логіку канонічного підписування, щоб включити його, і підтвердіть, що квитанція все ще проходить перевірку. Потім змініть поле після підписання і переконайтеся, що перевірка не проходить. Це змусить вас зрозуміти, як кожен байт канонічного кодування впливає на підпис.
Додаткове завдання 2: обчисліть SHA-256 хеш двох ваших квитанцій разом (конкатенація їхніх канонічних байтів у детермінованому порядку) і вставте отриманий дайджест як нове поле у третю квитанцію до підписання. Перевірте, що всі три квитанції все ще проходять перевірку. Ви щойно побудували одноступеневе доказ включення: будь-хто, хто має третю квитанцію, може довести, що перші дві існували на момент її підписання, не розкриваючи їхнього вмісту. Це патерн, який використовують квитанції з вибірковим розкриттям на великих масштабах (Merkle commitments, RFC 6962).
Криптографічні квитанції дають агентам ШІ аудиторський слід, який є:
Вони не замінюють валідацію вхідних даних, виконання політики або інфраструктуру ідентичності. Вони є основою для цих шарів. Коли ви розгортаєте агентів у регульованих навантаженнях, багатодержавних робочих процесах чи будь-яких умовах, де майбутній аудитор не може вам повністю довіряти, квитанції роблять аудиторський слід чесним.
Найголовніший висновок: квитанції доводять, хто що сказав і коли. Вони не доводять, що сказане було правдою або правильним. Утримуйте цю відмінність чітко. Це різниця між чесною системою походження і оманливою.
Коли будете готові перейти від цього уроку до розгортання агентів із підписаними квитанціями у реальній системі:
https://your-org.example.com/.well-known/agent-keys.json.Приєднуйтесь до Microsoft Foundry Discord, щоб спілкуватися з іншими студентами, відвідувати години консультацій і отримувати відповіді на питання щодо агентів ШІ.
Цей урок охоплює підписання однієї квитанції та ланцюжки з хешами. Ті самі примітиви складають кілька більш просунутих патернів, які ви можете зустріти в міру розвитку вашої системи управління:
authorization_*) та післявиконавчу (result_*) частини з незалежними підписами, корисно, коли рішення про дозвіл і спостережений результат створюються різними акторами або у різний час. Це додатково накладається на формат квитанції з цього уроку.result_hash. Реальні навантаження часто більш насичені, ніж просто результат виклику інструмента: логіка перед рішенням (прогноз моделі, розглянуті варіанти, докази та їх повнота, ризик, ланцюг відповідальності, результат проходження перевірки) можуть міститися в навантаженні, закритому однією квитанцією. Це утримує формат квитанції мінімальним і дозволяє еволюцію схем навантажень для кожної доменної області.signature.alg може містити ML-DSA-65 (постквантовий стандарт підпису NIST), коли потрібно здійснити міграцію. Плануйте період переходу з подвійним підписом квитанцій.Створення локальних агентів ШІ
Відмова від відповідальності: Цей документ було перекладено за допомогою сервісу штучного інтелекту для перекладу Co-op Translator. Хоча ми прагнемо до точності, будь ласка, майте на увазі, що автоматичні переклади можуть містити помилки або неточності. Оригінальний документ рідною мовою слід вважати авторитетним джерелом. Для критично важливої інформації рекомендується професійний людський переклад. Ми не несемо відповідальності за будь-які непорозуміння або неправильні тлумачення, що виникли внаслідок використання цього перекладу.