Ver el video de la lección: Asegurando agentes de IA con recibos criptográficos
(Video de la lección y miniatura serán agregados por el equipo de contenido de Microsoft después de la fusión, siguiendo el patrón de la lección 14 / 15.)
Esta lección cubrirá:
Después de completar esta lección, sabrás cómo:
Imagina que has desplegado un agente de IA para Contoso Travel. El agente lee solicitudes de clientes, llama a una API de vuelos para buscar opciones y reserva asientos en nombre del cliente. El último trimestre, el agente procesó 50,000 reservas.
Hoy llega un auditor. Hace una pregunta simple: “Muéstrame qué hizo tu agente.”
Entregas tus archivos de registro. El auditor los revisa y formula la pregunta más difícil: “¿Cómo sé que estos registros no fueron editados?”
Este es el problema de la traza de auditoría. La mayoría de los despliegues de agentes hoy dependen de:
Ninguno de estos puede responder la pregunta del auditor sin requerir que el auditor confíe en alguien (tú, tu proveedor de nube, tu vendedor de base de datos). Para uso interno, esa confianza suele ser aceptable. Para cargas reguladas (finanzas, salud, cualquier cosa sujeta a la Ley de IA de la UE), no lo es.
Los recibos criptográficos resuelven esto haciendo que cada acción del agente sea verificable de forma independiente. El auditor no necesita confiar en ti. Solo necesita tu clave pública y el recibo en sí.
Un recibo es un objeto JSON que registra lo que hizo un agente, firmado con una firma digital.
flowchart LR
A[El agente invoca una herramienta] --> B[Construir carga útil del recibo]
B --> C[Canonizar JSON RFC 8785]
C --> D[Hash SHA-256]
D --> E[Firma Ed25519]
E --> F[Recibo con firma]
F --> G[El auditor verifica sin conexión]
G --> H{¿Firma válida?}
H -- sí --> I[Prueba a prueba de manipulaciones]
H -- no --> J[Recibo rechazado]
Un recibo mínimo se parece a esto:
{
"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..."
}
}
Tres propiedades hacen el trabajo:
La firma. El recibo es firmado por la pasarela del agente usando una clave privada Ed25519. Cualquiera con la clave pública correspondiente puede verificar la firma sin conexión. Manipular cualquier campo invalida la firma.
Codificación canónica. Antes de firmar, el recibo se serializa usando el Esquema de Canonicalización JSON (JCS, RFC 8785). Esto asegura que dos implementaciones que producen el mismo recibo lógico produzcan una salida byte idéntica. Sin la canonicalización, diferentes serializadores JSON producirían diferentes firmas para el mismo contenido.
Encadenamiento por hash. El campo previous_receipt_hash enlaza cada recibo con el anterior. Eliminar o reordenar un recibo rompe cada recibo que vino después. La manipulación se vuelve visible a nivel de la cadena incluso si se ignoran firmas individuales.
Juntas, estas propiedades proporcionan tres garantías:
No necesitas una biblioteca especial para producir un recibo. Las primitivas criptográficas están ampliamente disponibles y la lógica son unas pocas docenas de líneas de Python.
Los ejercicios prácticos en code_samples/18-signed-receipts.ipynb recorren todo el flujo. La versión resumida:
import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize # RFC 8785 JSON canónico
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()}"
# Generar o cargar una clave de firma (en producción, almacenar en un almacén de claves)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key
# Construir la carga útil del recibo (aún sin firma)
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, hashear, firmar.
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
signature_bytes = signing_key.sign(message_hash).signature
# Adjuntar un objeto de firma estructurado.
receipt = {
**payload,
"signature": {
"alg": "EdDSA",
"sig": b64url_nopad(signature_bytes),
"public_key": b64url_nopad(bytes(verify_key)),
},
}
Esa es toda la tubería de firma. Los ejercicios en el notebook recorren cada paso.
La verificación es la operación 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:
# La firma es un objeto estructurado: {"alg", "sig", "public_key"}.
sig_obj = receipt.get("signature")
if not sig_obj or sig_obj.get("alg") != "EdDSA":
return False
# Reconstruir la carga útil que fue realmente firmada (todo excepto la firma).
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 función toma un recibo y devuelve True si la firma es válida, False de lo contrario. Sin llamadas a red, sin dependencia de servicios, sin confianza en terceros.
Para ver la detección de manipulaciones en acción, el notebook muestra:
tool_args_hash.Esta es la demostración práctica que los recibos evidencian manipulaciones: cualquier modificación, por pequeña que sea, rompe la firma.
Un solo recibo firmado protege una acción. Una cadena de recibos protege una secuencia.
flowchart LR
R0[Recibo 0<br/>génesis] --> 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 registra el hash del recibo anterior. Para eliminar silenciosamente el recibo 2, un atacante necesitaría:
previous_receipt_hash del recibo 3 (rompe la firma del recibo 3), OSi la clave privada está en una bóveda de hardware y publicas la clave pública con cada recibo, ninguno de estos ataques es factible sin detección.
El notebook muestra:
previous_receipt_hash de cada recibo coincida con el hash real del recibo anterior.Así produces una traza de auditoría que un auditor externo puede verificar sin confiar en ti.
Esta es la sección más importante de esta lección. Los recibos son poderosos, pero su poder es limitado.
Los recibos prueban tres cosas:
Los recibos NO prueban:
policy_id fue realmente evaluada, o que habría permitido esta acción si se hubiera verificado. El recibo registra lo que se afirmó, no lo que se implementó.Este límite importa por dos razones:
Un error común es asumir que “tenemos recibos” significa “estamos gobernados.” No es así. Los recibos son una base. La gobernanza es el sistema que construyes encima.
El código Python en esta lección es intencionalmente mínimo para que puedas leer cada línea y entender exactamente qué pasa. En producción, tienes dos opciones:
Construir directamente sobre las primitivas criptográficas. Las 50 líneas que viste arriba son suficientes para muchos casos de uso. PyNaCl (Ed25519) y el paquete jcs (JSON canónico) son bibliotecas bien mantenidas y auditadas.
Usar una biblioteca de recibos para producción. Varios proyectos de código abierto implementan el mismo patrón con características adicionales (rotación de claves, verificación por lotes, distribución del JWK Set, integración con motores de políticas):
draft-farley-acta-signed-receipts) actualmente en proceso de estandarización.protect-mcp (npm) y @veritasacta/verify (npm) proveen una implementación basada en Node para firma de recibos y verificación offline, pensada para envolver cualquier servidor MCP con una traza de auditoría a prueba de manipulaciones.La decisión entre hacer tu propio código y usar una biblioteca es similar a decidir entre escribir tu propia biblioteca JWT y usar una probada: ambas son razonables; la biblioteca ahorra tiempo y reduce la superficie de auditoría; el enfoque desde cero te fuerza a entender cada primitiva. Esta lección enseña el camino desde cero para que tengas la base para cualquiera de las dos opciones.
Prueba tu comprensión antes de pasar al ejercicio práctico.
1. Un recibo es firmado con la clave privada Ed25519 del agente. El auditor tiene solo la clave pública. ¿Puede el auditor verificar el recibo sin conexión?
2. Un atacante modifica el campo policy_id de un recibo para afirmar que estuvo gobernado por una política más permisiva. La firma fue sobre la carga útil original. ¿Qué pasa durante la verificación?
3. ¿Por qué el recibo incluye un tool_args_hash y un result_hash en lugar de los argumentos y el resultado en crudo?
4. El campo previous_receipt_hash enlaza cada recibo con su predecesor. Si un atacante elimina silenciosamente un recibo en medio de la cadena, ¿qué se vuelve inválido?
5. Un recibo se verifica correctamente. ¿Eso prueba que la acción del agente fue correcta, válida o cumplió con la política?
Abre code_samples/18-signed-receipts.ipynb y completa las cuatro secciones:
Desafío adicional 1: extiende el esquema del recibo con un campo adicional de tu elección (por ejemplo, un ID de solicitud para trazabilidad), actualiza la lógica de firma canónica para incluirlo, y confirma que el recibo sigue pasando la verificación. Luego modifica el campo después de firmar y confirma que la verificación falla. Esto te obliga a entender cómo cada byte de la codificación canónica contribuye a la firma. Desafío avanzado 2: Hash SHA-256 de dos de tus recibos juntos (concatena sus bytes canónicos en un orden determinista) e incrusta el resumen resultante como un nuevo campo en un tercer recibo antes de firmarlo. Verifica que los tres recibos aún se puedan procesar de ida y vuelta. Acabas de construir una prueba de inclusión de un solo paso: cualquiera que tenga el tercer recibo puede demostrar que los dos primeros existían en el momento en que fue firmado, sin necesidad de revelar su contenido. Este es el patrón que usan los recibos de divulgación selectiva a gran escala (compromisos Merkle, RFC 6962).
Los recibos criptográficos brindan a los agentes de IA una pista de auditoría que es:
No son un sustituto para la validación de entrada, la aplicación de políticas o la infraestructura de identidad. Son la base para esas capas. Cuando despliegas agentes en cargas de trabajo reguladas, flujos de trabajo multi-organización o en cualquier entorno donde no se pueda asumir que un auditor futuro confiará en ti, los recibos son cómo se hace que la pista de auditoría sea honesta.
La conclusión más importante: los recibos prueban quién dijo qué y cuándo. No prueban que lo dicho sea cierto o correcto. Mantén esa distinción clara. Es la diferencia entre un sistema de procedencia honesto y uno engañoso.
Cuando estés listo para avanzar de esta lección a desplegar agentes firmados con recibos en un entorno real:
https://your-org.example.com/.well-known/agent-keys.json.Únete al Discord de Microsoft Foundry para conocer a otros aprendices, asistir a horas de oficina y resolver tus dudas sobre Agentes de IA.
Esta lección cubre la firma de recibo único y secuencias encadenadas con hash. Las mismas primitivas componen varios patrones más avanzados que podrás encontrar a medida que madure tu postura de gobernanza:
authorization_*) y post-ejecución (result_*) con firmas independientes, útil cuando la decisión de autorización y el resultado observado son producidos por actores o tiempos diferentes. Esto se compone de forma aditiva sobre el formato de recibo enseñado en esta lección.result_hash. Las cargas reales suelen ser más ricas que un solo resultado de llamada de herramienta: razonamiento pre-decisional (predicción del modelo, opciones consideradas, evidencia y su integridad, postura de riesgo, cadena de responsabilidad, resultado de la puerta) pueden vivir dentro de la carga útil, sellados por un solo recibo. Esto mantiene el formato del recibo minimalista mientras permite que los esquemas de carga útil evolucionen según el dominio.signature.alg puede llevar ML-DSA-65 (el estándar NIST post-cuántico para firmas) cuando necesites migrar. Planea un período de transición donde los recibos estén firmados doblemente.Construyendo agentes de uso computacional (CUA)
(Por determinar por los mantenedores del currículo)
Descargo de responsabilidad: Este documento ha sido traducido utilizando el servicio de traducción automática Co-op Translator. Aunque nos esforzamos por la precisión, tenga en cuenta que las traducciones automatizadas pueden contener errores o inexactitudes. El documento original en su idioma nativo debe considerarse la fuente autorizada. Para información crítica, se recomienda una traducción profesional humana. No somos responsables de cualquier malentendido o interpretación errónea que surja del uso de esta traducción.