Смотреть видео урока: Защита AI-агентов с помощью криптографических квитанций
(Видео урока и миниатюра будут добавлены командой Microsoft по контенту после слияния, соответствующие шаблону урока 14 / 15.)
В этом уроке вы узнаете:
После этого урока вы сможете:
Представьте, что вы развернули AI-агента для Contoso Travel. Агент обрабатывает запросы клиентов, вызывает API авиакомпаний для поиска вариантов и бронирует места от имени клиента. В прошлом квартале агент обработал 50 000 бронирований.
Сегодня приходит аудитор и задаёт простой вопрос: «Покажите, что сделал ваш агент».
Вы отдаёте журналы. Аудитор смотрит на них и задаёт более сложный вопрос: «Как я могу быть уверен, что эти журналы не были отредактированы?»
Это и есть проблема аудиторского следа. Большинство развертываний агентов сегодня полагаются на:
Ни один из этих методов не отвечает на вопрос аудитора без необходимости доверять кому-то (вам, вашему облачному провайдеру, вендору базы данных). Для внутреннего использования такое доверие часто приемлемо, но для регулируемых нагрузок (финансы, здравоохранение, всё, что подпадает под EU AI Act) — нет.
Криптографические квитанции решают эту проблему, делая каждое действие агента независимо проверяемым. Аудитору не нужно доверять вам. Ему нужен только ваш публичный ключ и сама квитанция.
Квитанция — это 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 Canonicalization Scheme (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
Каждая квитанция фиксирует хэш предыдущей квитанции. Чтобы тихо удалить вторую квитанцию, злоумышленнику нужно либо:
previous_receipt_hash в квитанции 3 (испортит подпись квитанции 3), ИЛИЕсли приватный ключ хранится в аппаратном сейфе ключей, а публичный ключ публикуется с каждой квитанцией, ни одна из атак невозможна без обнаружения.
В ноутбуке рассматривается:
previous_receipt_hash каждой квитанции совпадает с фактическим хэшем предыдущей.Вот как создаётся аудиторский след, который внешний аудитор может проверить без доверия к вам.
Это самый важный раздел урока. Квитанции мощны, но их мощь ограничена.
Квитанции доказывают три вещи:
Квитанции НЕ доказывают:
policy_id, действительно была применена или что она разрешила бы это действие, если бы проверялась. Квитанция фиксирует лишь заявленное, а не выполняемое.Это разграничение важно по двум причинам:
Распространённая ошибка — думать, что «наличие квитанций» значит «есть управление». Это не так. Квитанции — фундамент. Управление — система, построенная поверх.
Код на Python в этом уроке намеренно минимален, чтобы вы могли читать каждую строку и полностью понимать происходящее. В производстве доступны два варианта:
Использовать криптографические примитивы напрямую. 50 строк, показанных выше, подходят во многих случаях. PyNaCl (Ed25519) и пакет jcs (каноническое JSON) — хорошо поддерживаемые и проверенные библиотеки.
Использовать библиотеку для квитанций. Несколько open-source проектов реализуют ту же схему с дополнительными функциями (ротация ключей, массовая проверка, распространение JWK Set, интеграция с движками политик):
draft-farley-acta-signed-receipts), который сейчас находится в процессе стандартизации.protect-mcp (npm) и @veritasacta/verify (npm) предоставляют Node-реализацию подписания и офлайн-проверки квитанций, предназначенные для обёртки любых MCP-серверов с аудиторским следом, защищённым от подделок.Выбор между «сделать самому» и «использовать библиотеку» аналогичен выбору между написанием собственной 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).
Криптографические квитанции предоставляют AI-агентам аудиторский след, который:
Они не заменяют проверку входных данных, выполнение политик или инфраструктуру идентификации. Это основа для этих слоёв. Когда вы внедряете агентов в регулируемые рабочие нагрузки, многоорганизационные процессы или любые ситуации, где будущий аудитор не может быть автоматически доверен, квитанции — это способ сделать аудит честным.
Самое главное: квитанции доказывают, кто что сказал и когда. Они не доказывают, что сказанное было правдой или правильным. Чётко разграничивайте это. Это разница между честной системой происхождения и вводящей в заблуждение.
Когда будете готовы перейти от этого урока к развертыванию агентов с подписанными квитанциями в рабочей среде:
https://your-org.example.com/.well-known/agent-keys.json.Присоединяйтесь к Microsoft Foundry Discord, чтобы встретиться с другими учащимися, посещать офис-часа и получить ответы на вопросы по AI-агентам.
Данный урок охватывает однократную подпись квитанции и цепочки на основе хэширования. Те же примитивы составляют ряд более продвинутых шаблонов, с которыми вы можете столкнуться по мере развития вашей системы управления:
authorization_*) и послеисполнительную (result_*) части с независимыми подписями, полезно, когда решение об авторизации и полученный результат создаются разными участниками или в разное время. Это дополняет формат квитанций, изученный в этом уроке.result_hash. Реальные данные чаще богаче, чем просто результат вызова инструмента: предпсихологические рассуждения (прогноз модели, рассмотренные варианты, доказательства и их полнота, оценка рисков, цепочка ответственности, результат пропуска) могут содержаться внутри полезной нагрузки, запечатанной одной квитанцией. Это сохраняет минимальный формат квитанции и позволяет эволюцию схем полезной нагрузки по областям.signature.alg может указывать ML-DSA-65 (стандарт подписи NIST для постквантовых алгоритмов) при необходимости миграции. Планируйте переходный период, когда квитанции будут подписываться двумя способами.Создание агентов для использования компьютера (CUA)
(Будет определён кураторами учебной программы)
Отказ от ответственности: Этот документ был переведен с использованием сервиса машинного перевода Co-op Translator. Несмотря на наши усилия по обеспечению точности, имейте в виду, что автоматический перевод может содержать ошибки или неточности. Оригинальный документ на его исходном языке следует считать авторитетным источником. Для получения критически важной информации рекомендуется обратиться к профессиональному человеческому переводу. Мы не несем ответственности за любые недоразумения или неправильные толкования, возникшие в результате использования этого перевода.