수업 영상 보기: 암호화 영수증으로 AI 에이전트 보안 강화
(수업 14 / 15 패턴에 맞춰 Microsoft 콘텐츠 팀에서 병합 후 수업 영상과 썸네일을 추가할 예정입니다.)
이 수업에서 다룰 내용:
이 수업을 완료하면 다음을 알게 됩니다:
Contoso Travel을 위한 AI 에이전트를 배포했다고 상상해보세요. 이 에이전트는 고객 요청을 읽고, 항공편 API를 호출하여 옵션을 조회하고, 고객을 대신해 좌석을 예약합니다. 지난 분기, 에이전트는 50,000건의 예약을 처리했습니다.
오늘 감사를 위해 감사관이 왔습니다. 그들은 간단한 질문을 합니다: “에이전트가 한 일을 보여주세요.”
당신은 로그 파일을 넘깁니다. 감사관은 그것들을 보고 더 어려운 질문을 합니다: “이 로그가 편집되지 않았다는 걸 어떻게 알죠?”
이것이 감사 추적 기록 문제입니다. 오늘날 대부분의 에이전트 배포는 다음에 의존합니다:
이들 어느 것도 감사관이 누군가(당신, 클라우드 제공자, 데이터베이스 벤더)를 신뢰하지 않고는 질문에 답할 수 없습니다. 내부 용도라면 그 신뢰가 허용되지만, 규제되는 워크로드(금융, 의료, EU AI 법안 대상 등)에는 그렇지 않습니다.
암호화 영수증은 각 에이전트 행동을 독립적으로 검증 가능하게 하여 이를 해결합니다. 감사관은 당신을 신뢰할 필요가 없습니다. 공개 키와 영수증만 필요합니다.
영수증은 에이전트가 수행한 일을 기록하는 JSON 객체로, 디지털 서명으로 서명되어 있습니다.
flowchart LR
A[에이전트가 도구를 호출함] --> B[영수증 페이로드 생성]
B --> C[JSON RFC 8785 표준화]
C --> 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 정규화 방식(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,
}
# JCS 바이트를 직접 정규화하고 서명. PureEdDSA가 내부적으로 해싱함.
canonical_bytes = canonicalize(payload)
signature_bytes = signing_key.sign(canonical_bytes).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)
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
이 함수는 영수증을 받고 서명이 올바르면 True, 아니면 False를 반환합니다. 네트워크 호출, 서비스 의존, 제3자 신뢰가 전혀 필요 없습니다.
변조 감지 실험에서는 노트북이 다음을 다룹니다:
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 서명 무효화),개인키가 하드웨어 키 보관소에 있고 공개 키를 각 영수증과 함께 공개하면 이런 공격은 모두 탐지 없이 불가능합니다.
노트북은 다음을 다룹니다:
previous_receipt_hash가 이전 영수증의 실제 해시와 일치하는지 검증.이렇게 하면 외부 감사관이 당신을 신뢰하지 않고도 검증 가능한 감사 추적 기록을 만들 수 있습니다.
이 수업에서 가장 중요한 부분입니다. 영수증은 강력하지만 그 힘엔 경계가 있습니다.
영수증이 증명하는 세 가지:
영수증이 증명하지 않는 것들:
policy_id에 언급된 정책이 실제 평가됐거나, 체크했다면 행동을 허용했을지. 영수증은 주장된 내용을 기록할 뿐, 강제된 내용을 기록하지 않음.이 경계가 중요한 이유 두 가지:
흔한 실수는 “영수증이 있으니 통제된다”고 가정하는 것입니다. 그렇지 않습니다. 영수증은 토대이며, 통제는 그 위에 구축하는 시스템입니다.
위 3번 항목은 별도 섹션이 필요합니다: 행동 영수증은 “이 키가 이 내용을 서명함”이지, “사람이 승인함”이 아닙니다. 고위험 행동(환불, 삭제, 송금 등)은 점점 더 그 누락된 진술을 요구하며, 이미 이 수업에서 구현한 기본 도구로 생성 가능합니다.
후속 노트북 code_samples/human-authorization-receipts.ipynb에서는 human.approval.v1이라는 두 번째 영수증 종류를 수업 영수증과 같은 형식(타입이 지정된 페이로드를 Ed25519로 canonical JCS 바이트에 서명하고, signature 객체는 서명된 바이트 외부에 있음)으로 추가합니다. 명명된 승인자가 실행 전에 전체 정규화 행동 및 그 다이제스트에 서명하며, 에이전트 행동 영수증은 같은 행동 다이제스트와 parent_approval_ref 승인 영수증 해시를 포함합니다. verify_chain 함수는 두 아티팩트를 별도의 고정 키 레지스트리 하에 검증하는데(승인자 키 vs 에이전트 키), 코드 경로는 공유하지만 권한자는 분리됩니다.
이 속성의 핵심: 사람이 이 정확한 행동을 승인했고, 에이전트가 바르게 실행했다. 거절 사례 테스트가 이 속성을 주장하지 않고 실제로 보장합니다:
각 실패는 고유한 사유로 거부하여 감사관이 권한 만료인지 실행 행동 변경인지 알 수 있게 합니다. 노트북 규칙: 서명된 승인만으로 권한 인정은 안 됨. 권한은 두 영수증이 실행 시 동일 정규화 행동에 묶여 있을 때만 존재. 인간 승인 영수증은 이 수업에서 정의한 교육용 조합이며, draft-farley-acta-signed-receipts에 정의된 영수증 종류는 아닙니다.
이 수업의 Python 코드는 일부러 최소화되어 있어 모든 줄을 읽고 정확히 무슨 일이 일어나는지 알 수 있습니다. 프로덕션에서는 두 가지 선택지가 있습니다:
암호학 기본 도구에 직접 구현하기. 위 50줄 코드는 많은 용도에 충분합니다. PyNaCl(Ed25519)과 jcs 패키지(정규화 JSON)는 잘 관리되고 감사된 라이브러리입니다.
프로덕션 영수증 라이브러리 사용하기. 여럿 오픈소스 프로젝트가 키 교체, 배치 검증, JWK 세트 배포, 정책 엔진 통합 등 부가 기능과 함께 같은 패턴을 구현합니다:
draft-farley-acta-signed-receipts, 개정 02)의 JCS 및 서명 범위 규칙을 사용합니다. 이 수업의 단순 교육용 영수증은 초안의 {payload, signature} 형식과 다르며, 준수 구현으로 제시되지 않습니다. 초안은 자체 와이어 형식 대상 구현을 위한 공유 적합성 테스트 벡터(agent-governance-testvectors)를 공개합니다.protect-mcp (npm)과 @veritasacta/verify (npm) 패키지는 Node 기반 영수증 서명 및 오프라인 검증 구현을 제공합니다. MCP 서버에 무결성 감사 추적을 래핑하며, 일시 중단된 행동이 WebAuthn 기반 데스크톱 흐름에서 승인 영수증을 행동 다이제스트에 묶어 발행하는 공동서명 플로우를 포함합니다. 이는 위 인간 승인 노트북과 같은 승인-영수증 패턴입니다.pip install nobulex)는 LangChain 및 CrewAI 통합과 함께 동일한 Ed25519+JCS 서명 패턴을 제공하며, 교차 검증 테스트 벡터와 OWASP PR #2210을 통한 컴플라이언스 맵핑을 포함합니다.직접 구현과 라이브러리 사용 사이 결정은 테스트된 JWT 라이브러리를 쓸지 직접 만들지의 고민과 동일합니다: 둘 다 합리적 선택, 라이브러리는 시간 절약 및 감사 면적 감소, 직접 구현은 기본 개념을 완전히 이해하게 합니다. 이 수업은 직접 구현 경로를 가르쳐 두 가지 선택지 모두에 기초를 제공합니다.
연습문제로 넘어가기 전 이해도를 점검하세요.
1. 영수증은 에이전트의 개인 Ed25519 키로 서명됩니다. 감사관은 공개 키만 가졌습니다. 감사관이 영수증을 오프라인에서 검증할 수 있을까요?
2. 공격자가 영수증의 policy_id 필드를 더 관대한 정책으로 주장하도록 수정했습니다. 서명은 원본 페이로드 위에 되어 있습니다. 검증 시 어떻게 됩니까?
3. 영수증에 원시 인수와 결과 대신 tool_args_hash 및 result_hash가 포함되는 이유는 무엇인가요?
4. previous_receipt_hash 필드는 각 영수증을 이전 것과 연결합니다. 공격자가 체인 중간에서 영수증 하나를 몰래 삭제하면 무엇이 무효가 되나요?
5. 영수증이 깨끗하게 검증되었다면, 에이전트의 행동이 올바르거나 건전하며 정책을 준수했다는 증거가 되나요?
code_samples/18-signed-receipts.ipynb를 열고 네 섹션을 모두 완성하세요:
도전 과제 1: 직접 선택한 추가 필드(예: 추적용 요청 ID)로 영수증 스키마를 확장하고 표준 서명 로직에 포함시키고 검증이 통과하는지 확인하세요. 그런 다음 서명 후 필드를 변경하고 검증 실패를 확인하세요. 이는 표준 인코딩의 모든 바이트가 서명에 어떻게 기여하는지 이해하는 데 도움이 됩니다.
도전 과제 2: 두 개의 영수증을 SHA-256 해시로 결합(표준 바이트를 결정적 순서로 연결)하고, 결과 해시를 세 번째 영수증의 새 필드에 삽입하여 서명하세요. 세 개의 영수증 모두가 계속 검증될 수 있음을 확인하세요. 이는 한 단계 포함 증명입니다: 세 번째 영수증 소지자는 첫 두 영수증이 서명 시점에 존재함을 내용 노출 없이 증명할 수 있습니다. 이는 선택적 공개 영수증이 대규모로 활용하는 패턴입니다(Merkle 커밋, RFC 6962).
암호화 영수증은 AI 에이전트에 다음과 같은 감사 추적을 제공합니다:
이들은 입력 검증, 정책 집행, 신원 인프라를 대체하지 않습니다. 오히려 그런 계층을 위한 기반입니다. 규제된 작업 부하, 다기관 워크플로우, 또는 미래 감사자가 신뢰를 전제로 할 수 없는 환경에서 에이전트를 배포할 때, 영수증은 감사 추적의 정직성을 보장하는 방법입니다.
가장 중요한 요점: 영수증은 누가 언제 무엇을 말했는지 증명합니다. 그것이 진실하거나 옳았다는 것을 증명하지는 않습니다. 이 차이를 분명히 유지하세요. 이는 정직한 출처 시스템과 오해를 불러오는 시스템의 차이입니다.
이 수업에서 실제 환경으로 영수증 서명 에이전트를 배포할 준비가 되었을 때:
https://your-org.example.com/.well-known/agent-keys.json).Microsoft Foundry Discord에 참여하여 다른 학습자들과 만나고, 오피스 아워에 참석해 AI 에이전트 질문에 답을 받으세요.
이 수업은 단일 영수증 서명과 해시 체인 시퀀스를 다룹니다. 동일한 프리미티브들은 거버넌스가 성숙됨에 따라 만나게 될 여러 고급 패턴을 구성합니다:
authorization_*)과 실행 후(result_*) 절반으로 나누어 독립 서명을 하고, 인가 결정과 관측 결과가 서로 다른 주체나 시점에서 생성될 때 유용합니다. 이 패턴은 본 수업에서 배운 영수증 형식 위에 추가로 적용됩니다.result_hash에 포함된 바이트를 봉인합니다. 실제 페이로드는 단일 도구 호출 결과보다 풍부한 경우가 많으며, 선행 결정 논리(모델 예측, 고려된 옵션, 증거와 그 완전성, 위험 자세, 책임 체인, 게이트 결과)가 모두 단일 영수증으로 봉인될 수 있습니다. 이는 영수증 형식을 최소한으로 유지하면서 도메인별로 페이로드 스키마가 진화할 수 있게 합니다.signature.alg 필드에 필요 시 NIST 포스트 양자 서명 표준 ML-DSA-65를 포함할 수 있습니다. 영수증 이중 서명 전환 기간도 계획하세요.면책 조항: 이 문서는 AI 번역 서비스 Co-op Translator를 사용하여 번역되었습니다. 정확성을 기하기 위해 노력하고 있으나, 자동 번역은 오류나 부정확한 부분이 있을 수 있음을 유의하시기 바랍니다. 원본 문서의 원어본이 권위 있는 자료로 간주되어야 합니다. 중요한 정보의 경우, 전문가의 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 당사는 책임을 지지 않습니다.