Ver el video de la lección: Asegurando agentes de IA con recibos criptográficos
(El video de la lección y la miniatura serán añadidos 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á:
Al 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.”
Le entregas tus archivos de registro. El auditor los revisa y hace la pregunta más difícil: “¿Cómo sé que estos registros no fueron editados?”
Este es el problema del rastro de auditoría. La mayoría de los despliegues de agentes hoy dependen de:
Ninguno de estos puede responder a la pregunta del auditor sin que el auditor tenga que confiar en alguien (tú, tu proveedor de nube, tu proveedor de base de datos). Para uso interno, esa confianza suele ser aceptable. Para cargas reguladas (finanzas, salud, cualquier cosa sujeta al Acta 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 mismo.
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[Canonicalizar JSON RFC 8785]
C --> E[Firmar bytes canónicos Ed25519]
E --> F[Recibo con firma]
F --> G[Auditor verifica fuera de línea]
G --> H{¿Firma válida?}
H -- yes --> I[Prueba a prueba de manipulaciones]
H -- no --> J[Recibo rechazado]
Un recibo minimalista se ve así:
{
"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 están haciendo el trabajo:
La firma. El recibo está firmado por la puerta de enlace 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 JSON Canonicalization Scheme (JCS, RFC 8785). Esto asegura que dos implementaciones que produzcan el mismo recibo lógico produzcan salida idéntica byte a byte. Sin canonicalización, diferentes serializadores JSON producirían firmas distintas para el mismo contenido.
Encadenamiento por hash. El campo previous_receipt_hash enlaza cada recibo con el anterior. Eliminar o reordenar un recibo rompe todos los recibos posteriores en la cadena. La manipulación se vuelve visible a nivel de cadena aun si se saltan firmas individuales.
Juntas estas propiedades proveen tres garantías:
No necesitas una biblioteca especial para producir un recibo. Los primitivos criptográficos están ampliamente disponibles y la lógica es unas pocas docenas de líneas en Python.
Los ejercicios prácticos en code_samples/18-signed-receipts.ipynb recorren todo el flujo completo. La versión resumen:
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()}"
# Generar o cargar una clave de firma (en producción, almacenar en una bóveda 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 y firmar los bytes JCS directamente. PureEdDSA hace el hash internamente.
canonical_bytes = canonicalize(payload)
signature_bytes = signing_key.sign(canonical_bytes).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 línea de firma. Los ejercicios en el notebook explican 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 firmada realmente (todo excepto la firma).
payload = {k: v for k, v in receipt.items() if k != "signature"}
canonical_bytes = canonicalize(payload)
try:
verify_key = signing.VerifyKey(b64url_decode(sig_obj["public_key"]))
verify_key.verify(canonical_bytes, 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 en caso contrario. No hay llamada a red, ni dependencia de servicios, ni confianza en terceros requerida.
Para ver la detección de manipulación en acción, el notebook muestra:
tool_args_hash.Esta es la demostración práctica de que los recibos son evidentes en cuanto a manipulaciones: cualquier modificación, por pequeña que sea, rompe la firma.
Un 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 un almacén de claves hardware y publicas la clave pública con cada recibo, ninguno de estos ataques es factible sin ser detectado.
El notebook muestra:
previous_receipt_hash de cada recibo coincida con el hash real del recibo previo.Así es como se produce un rastro de auditoría que un auditor externo puede verificar sin tener que 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 hubiera permitido esta acción si se hubiera verificado. El recibo registra lo que se dijo, no lo que se hizo cumplir.Este límite importa por dos razones:
Un error común es suponer que “tenemos recibos” significa “estamos gobernados”. No es así. Los recibos son una base. La gobernanza es el sistema que construyes encima.
El punto 3 arriba merece su propia sección: un recibo de acción dice “esta clave firmó este contenido,” nunca “un humano autorizó esto.” Para acciones de alto riesgo (reembolsos, eliminaciones, transferencias bancarias), los marcos de gobernanza exigen cada vez más esa declaración faltante, y se puede producir con los mismos primitivos que ya construiste en esta lección.
El notebook sucesor code_samples/human-authorization-receipts.ipynb añade un segundo tipo de recibo, human.approval.v1, con la misma forma de sobre que los recibos de la lección (una carga útil tipada firmada por Ed25519 sobre sus bytes canónicos JCS, con el objeto signature fuera de los bytes firmados). Un aprobador nombrado firma la acción canónica completa y su digest antes de la ejecución; el recibo de acción del agente lleva el mismo digest de la acción y una parent_approval_ref, el receipt_hash de la aprobación, la misma convención que previous_receipt_hash en la cadena que construiste arriba. Una verify_chain revisa ambos artefactos bajo registros de claves fijados separados (claves de aprobador vs claves de agente), así que el camino de código es compartido pero las autoridades nunca lo son.
La propiedad que esto otorga, expresada con cuidado: el humano aprobó esta acción exacta, y el agente ejecutó exactamente esa acción aprobada. Los arreglos de rechazo del notebook son los que hacen que esta propiedad sea real y no solo una afirmación:
Cada rechazo tiene una razón distinta, así un auditor leyendo un rechazo puede saber si la autoridad se volvió obsoleta o si la acción ejecutada cambió. La regla que enseña el notebook: una aprobación firmada no es autoridad por sí sola. La autoridad existe solo si ambos recibos aún se refieren a la misma acción canónica en el momento de la ejecución. El recibo de aprobación humana es una composición educativa definida por esta lección, no un tipo de recibo definido por draft-farley-acta-signed-receipts.
El código Python en esta lección es intencionalmente minimalista para que puedas leer cada línea y entender exactamente qué sucede. En producción, tienes dos opciones:
Construir directamente sobre los primitivos criptográficos. 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 en lote, distribución JWK Set, integración con motores de políticas):
draft-farley-acta-signed-receipts, revisión 02). El recibo plano educativo de esta lección difiere del sobre {payload, signature} del borrador y no se presenta como una implementación conforme. El borrador publica una suite de conformidad compartida (agent-governance-testvectors) para implementaciones dirigidas a su formato wire.protect-mcp (npm) y @veritasacta/verify (npm) proporcionan una implementación basada en Node para firma de recibos y verificación sin conexión, destinada a envolver cualquier servidor MCP con un rastro de auditoría evidente de manipulaciones, incluyendo un flujo de co-firma retenido en el que una acción pausada emite un recibo de aprobación vinculado al digest de la acción (respaldado por WebAuthn en el flujo de escritorio), el mismo patrón de recibo de aprobación del notebook de autorización humana arriba.pip install nobulex) proporciona el mismo patrón de firma Ed25519 + JCS en Python con integraciones LangChain y CrewAI, incluyendo vectores de prueba de validación cruzada publicados y un mapeo de cumplimiento aportado vía OWASP PR #2210.La decisión entre crear tu propia solución y usar una biblioteca es similar a la decisión entre escribir tu propia biblioteca JWT o usar una probada: ambas son razonables; la biblioteca ahorra tiempo y reduce la superficie de auditoría; el enfoque desde cero te obliga a entender cada primitivo. Esta lección enseña el camino desde cero para que tengas la base para cualquiera de las opciones.
Pon a prueba tu comprensión antes de pasar al ejercicio práctico.
1. Un recibo está 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 original. ¿Qué ocurre durante la verificación?
3. ¿Por qué el recibo incluye un tool_args_hash y result_hash en lugar de los argumentos y resultados en bruto?
4. El campo previous_receipt_hash enlaza cada recibo con su predecesor. Si un atacante elimina silenciosamente un recibo del medio de una 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 conforme a 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 rastreo), actualiza la lógica canónica de firma para incluirlo y confirma que el recibo aún puede verificarse correctamente. 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 adicional 2: Haz un hash SHA-256 combinando dos de tus recibos (concatena sus bytes canónicos en un orden determinista) y embebe el resumen resultante como un nuevo campo en un tercer recibo antes de firmarlo. Verifica que los tres recibos aún pueden verificarse. Has construido una prueba de inclusión de un paso: cualquiera con el tercer recibo puede probar que los dos primeros existían en el momento de la firma, sin revelar su contenido. Este es el patrón que usan los recibos de divulgación selectiva a escala (compromisos de Merkle, RFC 6962).
Los recibos criptográficos dan a los agentes de IA una pista de auditoría que es:
No son un sustituto para validación de entradas, aplicación de políticas o infraestructura de identidad. Son la base para esas capas. Cuando despliegas agentes en cargas reguladas, flujos de trabajo multiorganización o cualquier entorno donde un auditor futuro no pueda confiar en ti, los recibos son cómo haces honesta la pista de auditoría.
La lección más importante: los recibos prueban quién dijo qué y cuándo. No prueban que lo dicho sea verdadero o correcto. Sostén firmemente esta distinción. Es la diferencia entre un sistema de procedencia honesto y uno engañoso.
Cuando estés listo para pasar de esta lección a desplegar agentes con recibos firmados en un entorno real:
https://your-org.example.com/.well-known/agent-keys.json.Únete al Microsoft Foundry Discord para conectar con otros estudiantes, asistir a horas de oficina y resolver tus preguntas sobre agentes de IA.
Esta lección cubre la firma de recibos simples y secuencias encadenadas con hashes. Las mismas primitivas se combinan en varios patrones más avanzados que puedes encontrar a medida que madura 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 distintos o en tiempos distintos. Esto se suma al 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 a herramienta: el razonamiento previo a la decisión (predicción del modelo, opciones consideradas, evidencia y su completitud, postura de riesgo, cadena de responsabilidad, resultado de puerta) puede vivir dentro de la carga sellada por un solo recibo. Esto mantiene el formato de recibo minimalista mientras permite que los esquemas de carga evolucionen dominio por dominio.signature.alg puede llevar ML-DSA-65 (el estándar NIST de firmas post-cuánticas) cuando necesites migrar. Planea un periodo de transición donde los recibos estén firmados doblemente.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.