Смотрите видео урок: Защита AI-агентов с помощью криптографических квитанций
(Видео урока и миниатюра будут добавлены командой Microsoft по контенту после слияния, в соответствии с шаблоном уроков 14 / 15.)
В этом уроке вы узнаете:
После завершения этого урока вы будете уметь:
Представьте, что вы запустили AI-агента для 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 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 # RFC 8785 канонический 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()}"
# Сгенерировать или загрузить ключ для подписи (в производстве хранить в хранилище ключей)
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, в том же формате конверта, что и квитанции урока (типизированный payload, подписанный Ed25519 на каноническом SHA-256, с объектом signature вне подписанных байт). Названный утверждающий подписывает полное каноническое действие и его дайджест перед выполнением; квитанция действия агента содержит тот же дайджест действия и parent_approval_ref — receipt_hash утверждения, по аналогии с previous_receipt_hash в цепочке выше. Один verify_chain проверяет оба объекта в разных закрепленных реестрах ключей (ключи утвердителей vs ключи агентов), путь кода общий, но полномочия не пересекаются.
Этим достигается свойство, сформулированное аккуратно: человек утвердил это конкретное действие, и агент выполнил именно это утвержденное действие. В ноутбуке реализованы отказоустойчивые проверки, которые делают свойство реальным, а не декларативным:
Каждая ошибка возвращает уникальную причину, так что аудитор при чтении отказа видит, устарели ли полномочия или изменилось выполненное действие. Правило ноутбука: подписанное утверждение само по себе не является полномочиями. Полномочия существуют только, если обе квитанции по-прежнему связываются с одним и тем же каноническим действием во время выполнения. Путь со-сигнатуры в том же Internet-Draft, который используется в этом уроке (draft-farley-acta-signed-receipts), — это стандартный вариант этого шаблона.
Код Python в этом уроке преднамеренно минимален, чтобы вы могли прочитать каждую строку и точно понимать, что происходит. В продакшене есть два варианта:
Строить напрямую на криптографических примитивах. Те 50 строк, что вы видели выше, подходят для многих кейсов. PyNaCl (Ed25519) и пакет jcs (канонический JSON) — это хорошо поддерживаемые и проверенные библиотеки.
Использовать продакшн-библиотеку квитанций. Несколько открытых проектов реализуют тот же шаблон с дополнительными функциями (ротация ключей, пакетная проверка, распространение набора 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 квитанции, чтобы утверждать, что к действию применялась более либеральная политика. Подпись была сделана по исходному payload. Что произойдет при проверке?
3. Почему квитанция содержит tool_args_hash и result_hash, а не исходные аргументы и результат?
4. Поле previous_receipt_hash связывает каждую квитанцию с предыдущей. Если злоумышленник тайно удалит одну квитанцию из середины цепочки, что станет недействительным?
5. Квитанция успешно проверена. Доказывает ли это корректность, обоснованность или соответствие политике действия агента?
Откройте code_samples/18-signed-receipts.ipynb и выполните все четыре раздела:
Дополнительное задание 1: расширьте схему квитанции дополнительным полем на ваш выбор (например, идентификатор запроса для трассировки), обновите логику канонической подписи, чтобы включать его, и подтвердите, что квитанция успешно проходит проверку. Затем измените поле после подписи и подтвердите, что проверка не проходит. Это заставит вас понять, как каждый байт канонического кодирования влияет на подпись.
Дополнительное задание 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. Несмотря на наши усилия по обеспечению точности, имейте в виду, что автоматический перевод может содержать ошибки или неточности. Оригинальный документ на его исходном языке следует считать авторитетным источником. Для получения критически важной информации рекомендуется обратиться к профессиональному человеческому переводу. Мы не несем ответственности за любые недоразумения или неправильные толкования, возникшие в результате использования этого перевода.