ai-agents-for-beginners

수업 영상 보기: 암호화 영수증으로 AI 에이전트 보안 강화

(수업 영상과 썸네일은 병합 후 Microsoft 콘텐츠 팀에서 수업 14 / 15 패턴에 맞게 추가할 예정입니다.)

암호화 영수증으로 AI 에이전트 보안 강화

소개

이 수업에서는 다음을 다룹니다:

학습 목표

이 수업을 완료하면 다음을 할 수 있습니다:

문제: 에이전트의 감사 추적

Contoso Travel을 위해 AI 에이전트를 배포했다고 상상해 보세요. 에이전트는 고객 요청을 읽고, 항공편 API를 호출해 옵션을 조회한 후 고객을 대신해 좌석을 예약합니다. 지난 분기에는 에이전트가 50,000건의 예약을 처리했습니다.

오늘 감사인이 도착했습니다. 간단한 질문을 합니다: “당신의 에이전트가 무엇을 했는지 보여주세요.”

당신은 로그 파일을 건넵니다. 감사인은 더 어려운 질문을 합니다: “이 로그가 수정되지 않았다는 걸 어떻게 알죠?”

이것이 감사 추적 문제입니다. 오늘날 대부분의 에이전트 배포는 다음에 의존합니다:

이들 모두는 감사인이 누군가(당신, 클라우드 제공자, 데이터베이스 공급자)를 신뢰하지 않고는 질문에 답할 수 없습니다. 내부 용도라면 그 신뢰가 종종 받아들여지지만, 규제 대상 워크로드(금융, 의료, EU AI 법안 대상)에는 그렇지 않습니다.

암호화 영수증은 각 에이전트 행동을 독립적으로 검증 가능하도록 만들어 이 문제를 해결합니다. 감사인은 당신을 신뢰할 필요가 없습니다. 공개 키와 영수증만 있으면 됩니다.

암호화 영수증이란?

영수증은 에이전트가 수행한 작업을 기록한 JSON 객체로, 디지털 서명으로 서명되어 있습니다.

flowchart LR
    A[에이전트가 도구를 호출함] --> B[영수증 페이로드 생성]
    B --> C[JSON RFC 8785 표준화]
    C --> D[SHA-256 해시]
    D --> 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..."
  }
}

세 가지 속성이 역할을 합니다:

  1. 서명. 영수증은 에이전트 게이트웨이가 Ed25519 비공개 키로 서명합니다. 해당 공개 키를 가진 누구나 오프라인에서 서명을 검증할 수 있습니다. 어떤 필드를 변조하면 서명이 무효화됩니다.

  2. 정규 인코딩. 서명 전에 영수증은 JSON 정규화 방식(JCS, RFC 8785)으로 직렬화됩니다. 이는 동일한 논리적 영수증을 생성하는 두 구현체가 동일한 바이트를 출력하도록 보장합니다. 정규화가 없으면 서로 다른 JSON 직렬화기가 같은 내용에 대해 다른 서명을 생성할 수 있습니다.

  3. 해시 체인. previous_receipt_hash 필드는 각 영수증을 이전 영수증과 연결합니다. 영수증을 제거하거나 순서를 바꾸면 이후 모든 영수증이 깨집니다. 개별 서명을 무시하더라도 체인 수준에서 변조를 감지할 수 있습니다.

이 세 가지 속성은 세 가지 보장을 제공합니다:

Python으로 영수증 생성하기

영수증을 생성하는 데 특별한 라이브러리가 필요 없습니다. 암호화 원시 함수는 널리 사용 가능하며 로직은 몇 십 줄의 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,
}

# 정규화, 해시, 서명.
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
signature_bytes = signing_key.sign(message_hash).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)
    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

이 함수는 영수증을 받고 서명이 유효하면 True, 그렇지 않으면 False를 반환합니다. 네트워크 호출, 서비스 의존성, 제삼자 신뢰가 필요 없습니다.

변조 감지 실습에서는 다음을 진행합니다:

  1. 유효한 영수증을 생성하고 검증이 통과함을 확인.
  2. tool_args_hash 필드의 한 바이트를 수정.
  3. 검증을 다시 실행하고 실패함을 확인.

이는 영수증이 변조 감지 가능함을 보여줍니다: 아주 작은 수정도 서명을 깨뜨립니다.

다단계 에이전트를 위한 영수증 체인화

단일 서명 영수증은 한 행동을 보호하지만, 영수증 체인은 일련의 행동을 보호합니다.

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를 은밀히 제거하려면:

비공개 키가 하드웨어 키 저장소에 있고 공개 키가 영수증마다 공개된다면, 두 공격 모두 탐지 없이 불가능합니다.

노트북에서는 다음을 다룹니다:

  1. 세 개의 영수증 체인 구축.
  2. 각 영수증의 previous_receipt_hash가 직전 영수증의 실제 해시와 일치하는지 검증.
  3. 중간 영수증 하나를 변조하여 체인이 정확히 그 지점에서 깨지는 모습 확인.

이렇게 하면 외부 감사자가 당신을 신뢰하지 않고도 감사 추적을 검증할 수 있습니다.

영수증이 증명하는 것 (그리고 증명하지 않는 것)

이 부분이 수업에서 가장 중요합니다. 영수증은 강력하지만 한계가 분명합니다.

영수증이 증명하는 세 가지:

  1. 귀속: 특정 키가 특정 페이로드에 서명했다.
  2. 무결성: 페이로드가 서명 후 변경되지 않았다.
  3. 순서: 이 영수증은 해시 체인에서 저 영수증 이후에 생성되었다.

영수증이 증명하지 않는 것:

  1. 정확성: 에이전트의 행동이 올바른지 여부. 잘못된 답변에 대해서도 영수증이 깔끔하게 서명될 수 있다.
  2. 정책 준수: policy_id에 참조된 정책이 실제로 평가되었는지, 또는 이 행동이 허용되었는지. 영수증은 주장된 내용을 기록할 뿐 강제된 내용을 기록하지 않는다.
  3. 키를 넘는 신원: 영수증은 “이 키가 이 내용을 서명했다”고 말할 뿐 “이 인간이 승인했다”고 말하지 않는다. 키를 개인 또는 조직과 연결하려면 별도의 신원 인프라(디렉터리, 공개 키 레지스트리 등)가 필요하다.
  4. 입력의 진실성: 에이전트가 조작된 프롬프트를 받고 그에 따른 행동을 하더라도 영수증은 행동을 충실히 기록한다. 영수증은 입력 검증 아래에 있으며 대체품이 아니다.

이 경계가 중요한 이유 두 가지:

흔한 오류는 “영수증이 있으니 우리가 관리되고 있다”고 가정하는 것이다. 그렇지 않다. 영수증은 기초이며 관리 체계는 그 위에 구축하는 시스템이다.

인간이 정확한 행동을 승인했다는 증명

위 3번 항목은 별도 섹션이 필요하다: 행동 영수증은 “이 키가 이 내용을 서명했다”고 말할 뿐 “인간이 승인했다”고는 절대 말하지 않는다. 고위험 행동(환불, 삭제, 송금)의 경우 거버넌스 프레임워크는 점점 더 그 누락된 진술을 요구하며, 이 수업에서 이미 만든 원시 함수로도 생성 가능하다.

후속 노트북 code_samples/human-authorization-receipts.ipynb은 수업 영수증과 같은 봉투 형태(정규 SHA-256에 Ed25519 서명된 유형화된 페이로드, 서명 객체는 서명 바이트 바깥)에 두 번째 영수증 종류 human.approval.v1을 추가합니다. 명명된 승인자가 전체 정규화된 행동과 그 다이제스트를 실행 전에 서명하며, 에이전트의 행동 영수증은 같은 행동 다이제스트와 함께 parent_approval_ref, 즉 승인 영수증의 receipt_hash를 가지고 있는데, 이는 위에서 만든 체인의 previous_receipt_hash와 동일한 관례입니다. 하나의 verify_chain 함수가 두 아티팩트를 별개의 고정 키 레지스트리(승인자 키 대 에이전트 키) 하에 검증하므로 코드는 공유하지만 권한자는 절대 공유하지 않습니다.

이 속성의 정리된 표현: 인간이 이 정확한 행동을 승인했고, 에이전트는 정확히 그 승인된 행동을 실행했다. 노트북의 거부 테스트가 이 속성을 명시가 아닌 실체로 만듭니다:

각 실패에는 별개의 거부 사유가 있어 감사인이 권한 만료인지 실행된 행동 변경인지 알 수 있습니다. 노트북에서 가르치는 규칙: 서명된 승인은 단독으로 권한이 아니다. 실행 시 두 영수증 모두 같은 정규화 행동에 묶여야 권한이 존재한다. 이 수업과 동일한 인터넷 초안(draft-farley-acta-signed-receipts)의 공동 서명 경로가 이 패턴의 표준 트랙 형태입니다.

실제 적용 참고자료

이 수업의 Python 코드는 매우 간단하여 모든 줄을 읽고 정확히 어떤 일이 일어나는지 이해할 수 있습니다. 실제 환경에서는 두 가지 선택지가 있습니다:

  1. 암호화 원시 함수를 직접 사용하여 구축. 위의 50줄 코드는 많은 사용 사례에 충분합니다. PyNaCl(Ed25519)과 jcs 패키지(정규 JSON)는 잘 관리되고 감사받은 라이브러리입니다.

  2. 상용 영수증 라이브러리 사용. 몇몇 오픈소스 프로젝트는 동일한 패턴을 추가 기능(키 회전, 일괄 검증, JWK 세트 배포, 정책 엔진 통합)과 함께 구현합니다:

    • 이 수업에서 사용한 영수증 형식은 현재 표준화 절차 중인 IETF 인터넷 초안(draft-farley-acta-signed-receipts, 2차 개정)를 따르며, 독립 구현체들이 바이트 단위로 동일한 정규화 출력을 교차 검증하는 공유 적합성 테스트 스위트(agent-governance-testvectors)와 함께 제공됩니다.
    • Microsoft Agent Governance Toolkit은 Cedar 기반 정책 결정과 영수증을 조합합니다; 이 저장소의 튜토리얼 33에서 종단 간 예제를 볼 수 있습니다.
    • protect-mcp(npm)와 @veritasacta/verify(npm) 패키지는 영수증 서명과 오프라인 검증의 Node 구현체를 제공합니다. 이는 MCP 서버에 변조 감지 감사 추적을 감싸기 위한 것으로, 중단된 행동이 WebAuthn을 사용하는 데스크톱 플로우 속도의 승인 영수증을 방출하는 공동 서명 플로우를 포함합니다. 이는 위 인간 승인 노트북과 동일한 승인 영수증 패턴입니다.
    • nobulex Python SDK(pip install nobulex)는 LangChain과 CrewAI 통합을 포함한 동일한 Ed25519 + JCS 서명 패턴을 제공하며, 공개 검증 테스트 벡터와 OWASP PR #2210를 통한 준수 매핑도 포함합니다.

자체 구축과 라이브러리 사용 간 결정은 검증된 JWT 라이브러리를 쓸지 직접 만들지의 문제와 유사합니다: 두 경우 모두 합리적이며, 라이브러리는 시간 절약과 감사 범위를 줄이고, 직접 구현은 모든 원시 함수를 이해하도록 강제합니다. 이 수업은 직접 구현 방법을 가르쳐 두 가지 선택 모두에 기반을 제공합니다.

이해도 점검

실습으로 넘어가기 전에 이해도를 점검해 보세요.

1. 영수증은 에이전트의 비공개 Ed25519 키로 서명됩니다. 감사인은 공개 키만 가지고 있습니다. 감사인은 오프라인에서 영수증을 검증할 수 있나요?

답변 예. Ed25519 검증은 공개 키와 서명된 바이트만 필요합니다. 네트워크 호출이나 서비스 의존성이 없습니다. 이 속성 덕분에 영수증은 분리된 네트워크 환경, 다중 조직, 낮은 신뢰 감사 환경에서 유용합니다.

2. 공격자가 영수증의 policy_id 필드를 수정하여 더 관대한 정책에 의해 관리되었다고 주장합니다. 원래 페이로드에 대해 서명된 서명입니다. 검증 시 무슨 일이 일어납니까?

답변 검증 실패. 서명은 원본 페이로드의 정규화된 바이트에 대해 계산되었으며, 필드를 수정하면 정규화된 바이트가 변경되고, 이는 SHA-256 해시에 영향을 주며 서명이 유효하지 않게 됩니다. 공격자는 새로 유효한 서명을 생성하려면 개인 키가 필요하지만, 이를 가지고 있지 않습니다.

3. 왜 영수증에 원시 인수와 결과 대신 tool_args_hashresult_hash가 포함되나요?

답변 두 가지 이유가 있습니다. 첫째, 영수증은 원시 내용(개인 정보, 비즈니스 데이터)이 노출되는 문제가 있는 환경에서 보관하거나 전송해야 할 수도 있습니다. 해싱은 영수증을 작고 내용은 비공개로 유지하며, 감사자는 해시가 별도로 저장된 실제 내용의 사본과 일치하는지 검증합니다. 둘째, 해시는 고정된 크기를 가지므로, 해시를 포함한 영수증은 입력 및 출력이 얼마나 커도 크기가 제한됩니다.

4. previous_receipt_hash 필드는 각 영수증을 이전 영수증과 연결합니다. 공격자가 체인 중간의 한 영수증을 몰래 삭제한다면, 무엇이 유효하지 않게 되나요?

답변 삭제된 영수증 이후의 모든 영수증이 유효하지 않게 됩니다. 이들의 `previous_receipt_hash` 필드는 실제 체인과 더 이상 일치하지 않습니다 (참조하던 영수증이 존재하지 않거나 체인이 다른 이전 영수증을 가리키기 때문입니다). 삭제를 숨기려면 공격자는 이후 모든 영수증을 다시 서명해야 하는데, 이는 개인 키가 있어야 가능합니다.

5. 영수증이 정상적으로 검증되면 에이전트의 행동이 올바르거나 적절하거나 정책을 준수했다는 증거가 됩니까?

답변 아닙니다. 유효한 영수증은 세 가지를 증명합니다: 귀속(이 키가 이 내용을 서명했다), 무결성(내용이 변경되지 않았다), 순서(이 영수증이 저 영수증 다음에 생성됐다). 하지만 행동이 올바르다는 것, `policy_id`에 명시된 정책이 실제로 평가되었다는 것, 또는 에이전트가 모든 규칙을 준수했다는 것을 증명하지는 않습니다. 영수증은 에이전트 행위를 감사 가능하게 만들지만, 반드시 올바르다는 것을 증명하지는 않으며, 이것이 이 수업에서 가장 중요한 경계점입니다.

연습 문제

code_samples/18-signed-receipts.ipynb을 열고 네 개 섹션을 모두 완료하십시오:

  1. 섹션 1: 첫 번째 영수증에 서명하고 검증하세요.
  2. 섹션 2: 영수증을 변조하고 검증 실패를 관찰하세요.
  3. 섹션 3: 세 개의 영수증 체인을 만들고 체인 무결성을 검증하세요.
  4. 섹션 4: Microsoft Agent Framework로 만든 에이전트에 이 패턴을 적용하세요: 도구 호출을 영수증 서명으로 래핑한 후 독립적으로 영수증을 검증합니다.

확장 도전 1: 요청 추적용 요청 ID 등 원하는 필드를 영수증 스키마에 추가하고, 이에 포함되도록 정규화된 서명 로직을 업데이트한 뒤, 영수증이 여전히 검증-복원 되는지 확인하세요. 그런 다음 서명 후 필드를 변경하고 검증 실패를 확인하세요. 이 과정은 정규화 인코딩의 모든 바이트가 서명에 어떻게 기여하는지 이해하는 데 도움이 됩니다.

확장 도전 2: 두 개 영수증의 SHA-256 해시를 함께 계산(정규화 바이트를 결정론적으로 연결)하여 세 번째 영수증에 새로운 필드로 삽입한 다음 서명하세요. 세 영수증 모두가 여전히 검증-복원 가능한지 확인하세요. 이렇게 하면 1단계 포함 증명을 만든 셈입니다: 세 번째 영수증 소지자는 첫 번째 두 영수증이 서명 시점에 존재했다는 것을 내용 공개 없이 증명할 수 있습니다. 이 패턴은 대규모 선택적 공개 영수증에 사용됩니다(Merkle 커밋, RFC 6962).

결론

암호화된 영수증은 AI 에이전트에게 다음과 같은 감사 추적을 제공합니다:

이것은 입력 검증, 정책 집행, 신원 인프라를 대체하지 않습니다. 그 기반을 마련하는 역할을 합니다. 규제된 업무, 다중 조직 워크플로, 미래의 감사자가 신뢰할 수 없는 환경에 에이전트를 배포할 때 영수증이 감사 추적을 정직하게 만드는 방법입니다.

가장 중요한 요점: 영수증은 누가 언제 무엇을 말했는지 증명하지만, 말한 내용이 진실이거나 옳았다는 것을 증명하지는 않습니다. 이 차이를 명확히 인식해야 합니다. 정직한 출처 시스템과 오해를 불러일으키는 시스템의 차이입니다.

운영 체크리스트

이 수업을 졸업하고 실제 환경에 영수증 서명 에이전트를 배포할 준비가 되면:

AI 에이전트 보안에 대해 더 궁금한가요?

Microsoft Foundry Discord에서 다른 학습자들과 만나고, 오피스 아워에 참석하며, AI 에이전트 관련 질문을 하세요.

이 수업 이후

이 수업은 단일 영수증 서명과 해시 체인 시퀀스를 다룹니다. 동일한 프리미티브는 거버넌스 태세가 성숙해질 때 만날 수 있는 여러 고급 패턴으로 구성됩니다:

추가 리소스

이전 수업

로컬 AI 에이전트 만들기


면책 조항: 이 문서는 AI 번역 서비스 Co-op Translator를 사용하여 번역되었습니다. 정확성을 기하기 위해 노력하고 있으나, 자동 번역은 오류나 부정확한 부분이 있을 수 있음을 유의하시기 바랍니다. 원본 문서의 원어본이 권위 있는 자료로 간주되어야 합니다. 중요한 정보의 경우, 전문가의 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 당사는 책임을 지지 않습니다.