Veja o vídeo da lição: Protegendo Agentes de IA com Recibos Criptográficos
(Vídeo da lição e miniatura a serem adicionados pela equipa de conteúdos da Microsoft após integração, seguindo o padrão das lições 14 / 15.)
Esta lição irá abordar:
Após completar esta lição, saberá como:
Imagine que implementou um agente de IA para a Contoso Travel. O agente lê pedidos dos clientes, chama uma API de voos para procurar opções e reserva lugares em nome do cliente. No último trimestre, o agente processou 50.000 reservas.
Hoje chega um auditor. Ele faz uma pergunta simples: “Mostre-me o que o seu agente fez.”
Você entrega os seus ficheiros de log. O auditor olha para eles e faz a pergunta mais difícil: “Como posso saber que estes logs não foram editados?”
Este é o problema da trilha de auditoria. A maioria das implementações de agentes hoje em dia depende de:
Nenhum destes consegue responder à pergunta do auditor sem exigir que ele confie em alguém (você, o seu fornecedor de cloud, o fornecedor da sua base de dados). Para uso interno, essa confiança é muitas vezes aceitável. Para cargas de trabalho regulamentadas (finanças, saúde, qualquer coisa sujeita ao AI Act da UE), não é.
Os recibos criptográficos resolvem isto tornando cada ação do agente verificável de forma independente. O auditor não precisa de confiar em si. Só precisa da chave pública e do próprio recibo.
Um recibo é um objeto JSON que regista o que um agente fez, assinado com uma assinatura digital.
flowchart LR
A[Agente invoca uma ferramenta] --> B[Construir payload do recibo]
B --> C[Canonicalizar JSON RFC 8785]
C --> D[Hash SHA-256]
D --> E[Assinar Ed25519]
E --> F[Recibo com assinatura]
F --> G[Auditor verifica offline]
G --> H{Assinatura válida?}
H -- yes --> I[Prova à prova de adulterações]
H -- no --> J[Recibo rejeitado]
Um recibo mínimo é assim:
{
"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..."
}
}
Três propriedades estão a fazer o trabalho:
A assinatura. O recibo é assinado pelo gateway do agente usando uma chave privada Ed25519. Quem tiver a chave pública correspondente pode verificar a assinatura offline. Adulterar qualquer campo invalida a assinatura.
Codificação canónica. Antes de assinar, o recibo é serializado usando o Esquema de Canonicalização JSON (JCS, RFC 8785). Isto garante que duas implementações que produzem o mesmo recibo lógico produzem uma saída byte-idêntica. Sem canonização, diferentes serializadores JSON originariam assinaturas diferentes para o mesmo conteúdo.
Encadeamento por hash. O campo previous_receipt_hash liga cada recibo ao anterior. Remover ou reordenar um recibo quebra cada recibo que veio depois dele. A adulteração torna-se visível a nível da cadeia mesmo que se ignorem assinaturas individuais.
Em conjunto, estas propriedades fornecem três garantias:
Não precisa de uma biblioteca especial para produzir um recibo. As primitivas criptográficas são amplamente disponíveis e a lógica são algumas dezenas de linhas em Python.
Os exercícios práticos em code_samples/18-signed-receipts.ipynb percorrem o fluxo completo. A versão resumida:
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize # JSON canónico segundo a 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()}"
# Gerar ou carregar uma chave de assinatura (em produção, armazenar num cofre de chaves)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
# Construir o payload do recibo (ainda sem assinatura)
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,
}
# Canonicalizar, hash, assinar.
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
signature_bytes = signing_key.sign(message_hash).signature
# Anexar um objeto de assinatura estruturado.
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
Esse é todo o pipeline de assinatura. Os exercícios no notebook explicam cada passo.
A verificação é a operação inversa:
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:
# A assinatura é um objeto estruturado: {"alg", "sig", "public_key"}.
sig_obj = receipt.get("signature")
if not sig_obj or sig_obj.get("alg") != "EdDSA":
return False
# Reconstrua o conteúdo que foi realmente assinado (tudo excepto a assinatura).
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
Esta função recebe um recibo e retorna True se a assinatura for válida, False caso contrário. Sem chamada de rede, sem dependência de serviço, sem necessidade de confiar em terceiros.
Para ver a detecção de adulterações em ação, o notebook percorre:
tool_args_hash.Esta é a demonstração prática que os recibos evidenciam adulteração: qualquer modificação, por menor que seja, rompe a assinatura.
Um único recibo assinado protege uma ação. Uma cadeia de recibos protege uma sequência.
flowchart LR
R0[Recibo 0<br/>génese] --> R1[Recibo 1]
R1 --> R2[Recibo 2]
R2 --> R3[Recibo 3]
R1 -. previous_receipt_hash .-> R0
R2 -. previous_receipt_hash .-> R1
R3 -. previous_receipt_hash .-> R2
Cada recibo regista o hash do recibo anterior. Para remover silenciosamente o recibo 2, um atacante teria de:
previous_receipt_hash do recibo 3 (quebra a assinatura do recibo 3), OUSe a chave privada estiver num cofre de chaves hardware e você publicar a chave pública com cada recibo, nenhum destes ataques é possível sem ser detetado.
O notebook percorre:
previous_receipt_hash de cada recibo corresponde ao hash real do recibo anterior.É assim que produz uma trilha de auditoria que um auditor externo pode verificar sem confiar em si.
Esta é a secção mais importante desta lição. Os recibos são poderosos, mas o seu poder é limitado.
Os recibos provam três coisas:
Os recibos NÃO provam:
policy_id foi realmente avaliada ou que teria permitido esta ação se verificada. O recibo regista o que foi alegado, não o que foi aplicado.Este limite importa por duas razões:
Um erro comum é assumir que “temos recibos” significa “estamos regulados.” Não significa. Recibos são uma base. Regulação é o sistema que constrói por cima.
O ponto 3 acima merece uma secção própria: um recibo de ação diz “esta chave assinou este conteúdo,” nunca “um humano autorizou isto.” Para ações de alto risco (reembolsos, eliminações, transferências bancárias), estruturas de governação cada vez mais exigem exactamente essa declaração em falta, e é produzível com as mesmas primitivas que já construiu nesta lição.
O notebook complementar code_samples/human-authorization-receipts.ipynb acrescenta um segundo tipo de recibo, human.approval.v1, na mesma estrutura de envelope que os recibos da lição (um payload tipado assinado por Ed25519 sobre o seu SHA-256 canónico, com o objeto signature fora dos bytes assinados). Um aprovador nomeado assina a ação canónica completa e o seu resumo antes da execução; o recibo de ação do agente carrega o mesmo resumo da ação e um parent_approval_ref, o receipt_hash da aprovação, a mesma convenção que previous_receipt_hash na cadeia construída acima. Uma função verify_chain percorre ambos os artefactos em registos de chaves fixos separados (chaves dos aprovadores vs chaves dos agentes), portanto o caminho do código é partilhado mas as autoridades nunca são.
A propriedade adquirida, expressa cuidadosamente: o humano aprovou esta ação exata, e o agente executou exatamente essa ação aprovada. Os exemplos de recusa no notebook é que tornam a propriedade real em vez de afirmada:
Cada falha recusa com um motivo distinto, para que um auditor lendo uma recusa possa saber se a autoridade ficou obsoleta ou se a ação executada mudou. A regra que o notebook ensina: uma aprovação assinada não é autoridade por si só. A autoridade existe apenas se ambos os recibos ainda ligam à mesma ação canónica no momento da execução. O caminho da cosignatura no mesmo Internet-Draft que esta lição segue (draft-farley-acta-signed-receipts) é a forma padrão deste padrão.
O código Python nesta lição é intencionalmente minimalista para que possa ler cada linha e entender exatamente o que está a acontecer. Em produção, tem duas opções:
Construir diretamente sobre as primitivas criptográficas. As 50 linhas que viu acima são suficientes para muitos casos de uso. PyNaCl (Ed25519) e o pacote jcs (JSON canónico) são bibliotecas bem mantidas e auditadas.
Usar uma biblioteca de recebos para produção. Vários projetos open-source implementam o mesmo padrão com funcionalidades adicionais (rotação de chaves, verificação em lote, distribuição de conjuntos JWK, integração com motores de políticas):
draft-farley-acta-signed-receipts, revisão 02) atualmente no processo de normalização, com uma suíte de conformidade partilhada (agent-governance-testvectors) que implementações independentes verificam mutuamente para saída canónica byte-idêntica.protect-mcp (npm) e @veritasacta/verify (npm) fornecem uma implementação Node de assinatura e verificação offline de recibos, destinada a empacotar qualquer servidor MCP com uma trilha de auditoria à prova de adulteração, incluindo um fluxo em que uma ação em pausa emite um recibo de aprovação ligado ao resumo da ação (suportado por WebAuthn no fluxo desktop), o mesmo padrão de recibo de aprovação do notebook de autorização humana acima.pip install nobulex) fornece o mesmo padrão de assinatura Ed25519 + JCS em Python com integrações LangChain e CrewAI, incluindo vetores de teste publicados para validação cruzada e um mapeamento de conformidade contribuído através do OWASP PR #2210.A decisão entre construir o seu próprio ou usar uma biblioteca espelha a decisão entre escrever a sua própria biblioteca JWT e usar uma testada: ambos são razoáveis; a biblioteca poupa tempo e reduz a superfície de auditoria; a abordagem do zero obriga a entender cada primitiva. Esta lição ensina o caminho do zero para que tenha a base para qualquer escolha.
Teste a sua compreensão antes de passar para o exercício prático.
1. Um recibo é assinado com a chave privada Ed25519 do agente. O auditor tem apenas a chave pública. Pode o auditor verificar o recibo offline?
2. Um atacante modifica o campo policy_id de um recibo para alegar que estava regido por uma política mais permissiva. A assinatura foi feita sobre o payload original. O que acontece durante a verificação?
3. Porque é que o recibo inclui um tool_args_hash e result_hash em vez dos argumentos e resultado brutos?
4. O campo previous_receipt_hash liga cada recibo ao seu predecessor. Se um atacante eliminar silenciosamente um recibo do meio da cadeia, o que se torna inválido?
5. Um recibo é verificado com sucesso. Isso prova que a ação do agente foi correta, válida ou conforme a política?
Abra code_samples/18-signed-receipts.ipynb e complete todas as quatro secções:
Desafio adicional 1: estenda o esquema do recibo com um campo adicional à sua escolha (por exemplo, um ID de pedido para rastreio), atualize a lógica clássica de assinatura para incluí-lo e confirme que o recibo ainda completa o ciclo de verificação. Depois modifique o campo após a assinatura e confirme que a verificação falha. Isto obriga a entender como cada byte da codificação canónica contribui para a assinatura.
Desafio adicional 2: faça o hash SHA-256 de dois dos seus recibos juntos (concatene os seus bytes canónicos numa ordem determinística) e incorpore o digest resultante como um novo campo num terceiro recibo antes de o assinar. Verifique que os três recibos ainda completam o ciclo. Acabou de construir uma prova de inclusão num só passo: qualquer pessoa com o terceiro recibo pode provar que os dois primeiros existiam à data da sua assinatura, sem precisar revelar o seu conteúdo. Este é o padrão que os recibos de divulgação seletiva usam em larga escala (compromissos de Merkle, RFC 6962).
Recibos criptográficos dão aos agentes de IA um trilho de auditoria que é:
Não são um substituto para validação de entrada, aplicação de políticas ou infraestrutura de identidade. São a base para essas camadas. Quando estiver a implementar agentes em cargas de trabalho reguladas, fluxos de trabalho multi-organização ou qualquer ambiente onde não se possa assumir que um auditor futuro confie em si, os recibos são a forma de garantir um trilho de auditoria honesto.
A lição mais importante: os recibos provam quem disse o quê e quando. Não provam que o que foi dito é verdade ou está correto. Tenha essa distinção bem presente. É a diferença entre um sistema de proveniência honesto e um enganador.
Quando estiver pronto para avançar desta lição para implementar agentes assinados com recibos num ambiente real:
https://your-org.example.com/.well-known/agent-keys.json.Junte-se ao Microsoft Foundry Discord para encontrar outros aprendizes, participar em horas de atendimento e obter respostas às suas perguntas sobre agentes de IA.
Esta lição cobre assinatura de recibos únicos e sequências encadeadas por hash. Os mesmos primitivos compõem vários padrões mais avançados que poderá encontrar à medida que a sua postura de governação evolui:
authorization_*) e pós-execução (result_*) com assinaturas independentes, útil quando a decisão de autorização e o resultado observado são produzidos por atores diferentes ou em momentos diferentes. Isto adiciona-se ao formato do recibo ensinado nesta lição.result_hash. Cargas reais são tipicamente mais ricas do que um único resultado de chamada de ferramenta: raciocínio pré-decisão (previsão de modelo, opções consideradas, evidências e sua completude, postura de risco, cadeia de responsabilidade, resultado do gate) podem estar dentro da carga, selados por um único recibo. Isto mantém o formato do recibo minimalista enquanto permite evolução dos esquemas das cargas por domínio.signature.alg pode conter ML-DSA-65 (o padrão NIST para assinatura pós-quântica) quando precisar de migrar. Planeie um período de transição em que os recibos são assinados em duplicado.Aviso Legal: Este documento foi traduzido utilizando o serviço de tradução automática Co-op Translator. Embora nos esforcemos pela precisão, esteja ciente de que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autorizada. Para informações críticas, recomenda-se tradução profissional humana. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas resultantes da utilização desta tradução.