Watch the lesson video: Securing AI Agents with Cryptographic Receipts
(Vídeo da aula e miniatura a serem adicionados pela equipa de conteúdos da Microsoft após a fusão, seguindo o padrão das aulas 14 / 15.)
Esta aula irá abordar:
Após concluir esta aula, saberá como:
Imagine que implementou um agente IA para a Contoso Travel. O agente lê pedidos dos clientes, faz chamadas a 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.”
Entrega os seus ficheiros de log. O auditor analisa-os e faz a pergunta mais difícil: “Como posso saber que estes registos não foram editados?”
Este é o problema do registo de auditoria. A maioria das implementações de agentes hoje em dia baseiam-se em:
Nenhum destes consegue responder à pergunta do auditor sem que este tenha de confiar em alguém (em si, no seu fornecedor de nuvem, no fornecedor da base de dados). Para uso interno, essa confiança é muitas vezes aceitável. Para cargas de trabalho reguladas (finanças, saúde, qualquer coisa sujeita ao Regulamento Europeu de IA), não é.
Recibos criptográficos resolvem isto, tornando cada ação do agente independentemente verificável. O auditor não precisa confiar em si. Só necessita da sua 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[O agente invoca uma ferramenta] --> B[Construir payload do recibo]
B --> C[Canonizar 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ção]
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 fazem o trabalho:
A assinatura. O recibo é assinado pelo gateway do agente usando uma chave privada Ed25519. Qualquer pessoa com 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 assegura que duas implementações que produzam o mesmo recibo lógico produzam uma saída byte-idêntica. Sem canonicidade, diferentes serializadores JSON produziriam 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 todos os recibos seguintes. A adulteração torna-se visível a nível da cadeia, mesmo que as assinaturas individuais sejam contornadas.
Estas propriedades juntas proporcionam três garantias:
Não precisa de uma biblioteca especial para produzir um recibo. As primitivas criptográficas estão amplamente disponíveis e a lógica é de poucas dezenas de linhas em Python.
Os exercícios práticos em code_samples/18-signed-receipts.ipynb apresentam todo o fluxo. A versão resumida:
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize # JSON canónico 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 a carga útil 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)),
},
}
Este é todo o pipeline de assinatura. Os exercícios no notebook explicam cada passo detalhadamente.
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
# Reconstrói a carga útil que foi realmente assinada (tudo exceto 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 chamadas à rede, sem dependência de serviços, sem necessidade de confiar em terceiros.
Para ver a deteção de adulterações em ação, o notebook mostra:
tool_args_hash.Este é um exemplo prático de que os recibos evidenciam adulterações: qualquer modificação, por menor que seja, quebra 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 hardware e publicar a chave pública com cada recibo, nenhum destes ataques é factível sem deteção.
O notebook explica:
previous_receipt_hash de cada recibo corresponde ao hash real do recibo anterior.É assim que se produz um registo de auditoria que um auditor externo pode verificar sem confiar em si.
Esta é a secção mais importante desta aula. Recibos são poderosos, mas o seu poder é limitado.
Os recibos provam três coisas:
Os recibos NÃO provam:
policy_id foi efectivamente avaliada ou que teria permitido esta ação se tivesse sido verificada. O recibo regista o que foi reclamado, não o que foi aplicado.Este limite é importante por duas razões:
Um erro comum é assumir que “temos recibos” significa “estamos governados”. Não significa. Recibos são uma base. Governança é o sistema que constrói em cima.
O código Python nesta aula é intencionalmente minimalista para que possa ler linha a linha e entender exatamente o que acontece. 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 recibos em 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 JWK Set, integração com motores de política):
draft-farley-acta-signed-receipts) atualmente no processo de normalização.protect-mcp (npm) e @veritasacta/verify (npm) fornecem uma implementação Node para assinatura e verificação offline de recibos, destinada a proteger qualquer servidor MCP com registo de auditoria evidenciando adulteração.A decisão entre construir a sua solução ou usar uma biblioteca é como decidir entre escrever a sua própria biblioteca JWT ou usar uma testada: ambas razoáveis; a biblioteca poupa tempo e reduz a superfície de auditoria; o caminho do zero força a compreender cada primitiva. Esta aula ensina o caminho do zero para lhe dar a base para qualquer escolha.
Teste a sua compreensão antes de avançar 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 governado por uma política mais permissiva. A assinatura estava sobre a carga original. O que acontece durante a verificação?
3. Porque é que o recibo inclui um tool_args_hash e um result_hash em vez dos argumentos e resultados crus?
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 fica inválido?
5. Um recibo verifica corretamente. Isso prova que a ação do agente estava correcta, sólida ou em conformidade com a política?
Abra code_samples/18-signed-receipts.ipynb e complete as quatro secções:
Desafio extra 1: alargue o esquema do recibo com um campo adicional à sua escolha (por exemplo, um ID de pedido para rastreio), atualize a lógica canónica de assinatura para o incluir, e confirme que o recibo passa ainda a verificação. Depois modifique o campo após a assinatura e confirme que a verificação falha. Isto obriga a compreender como cada byte da codificação canónica contribui para a assinatura. Desafio extra 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 resumo resultante como um novo campo num terceiro recibo antes de o assinar. Verifique que os três recibos ainda podem ser revertidos para o seu estado original. Acabou de construir uma prova de inclusão em um passo: qualquer pessoa que possua o terceiro recibo pode provar que os dois primeiros existiam na altura em que foi assinado, sem precisar revelar o seu conteúdo. Este é o padrão que os recibos de divulgação seletiva usam em grande escala (compromissos de Merkle, RFC 6962).
Os recibos criptográficos fornecem aos agentes de IA uma trilha de auditoria que é:
Eles não substituem a validação de entrada, aplicação de políticas ou infraestrutura de identidade. São uma base para essas camadas. Quando está a colocar agentes em cargas de trabalho reguladas, fluxos de trabalho multi-organização, ou qualquer cenário em que um auditor futuro não possa presumir confiar em si, os recibos são a forma de tornar a trilha de auditoria honesta.
O ponto mais importante: os recibos provam quem disse o quê, quando. Não provam que o que foi dito era verdade ou correto. Mantenha essa distinção bem clara. É a diferença entre um sistema de proveniência honesto e um enganador.
Quando estiver pronto para passar desta lição para a implementação de agentes assinados com recibos num ambiente real:
https://your-org.example.com/.well-known/agent-keys.json.Junte-se ao Microsoft Foundry Discord para conhecer outros aprendizes, participar em horas de atendimento e obter respostas às suas perguntas sobre Agentes de IA.
Esta lição cobre a assinatura com recibo único 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 se desenvolve:
authorization_*) e pós-execução (result_*), útil quando a decisão de autorização e o resultado observado são produzidos por atores diferentes ou em momentos diferentes. Isto compõe-se de forma aditiva por cima do formato de recibo ensinado nesta lição.result_hash. Cargas reais são frequentemente mais ricas do que o resultado de uma única chamada de ferramenta: raciocínio pré-decisão (predição de modelo, opções consideradas, evidência e sua completude, postura de risco, cadeia de responsabilidade, resultado do gate) podem todas residir na carga, seladas por um único recibo. Isto mantém o formato do recibo minimalista ao mesmo tempo que permite que os esquemas de carga evoluam domínio a domínio.signature.alg pode transportar ML-DSA-65 (o standard NIST para assinaturas pós-quânticas) quando precisar de migrar. Planeie um período de transição em que os recibos tenham assinaturas duplas.Construir Agentes de Uso de Computador (CUA)
(A determinar pelos mantenedores do currículo)
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.