ai-agents-for-beginners

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

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

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

소개

이 수업에서 다룰 내용:

학습 목표

이 수업을 완료하면 다음을 알게 됩니다:

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

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..."
  }
}

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

  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,
}

# 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자 신뢰가 전혀 필요 없습니다.

변조 감지 실험에서는 노트북이 다음을 다룹니다:

  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에서는 human.approval.v1이라는 두 번째 영수증 종류를 수업 영수증과 같은 형식(타입이 지정된 페이로드를 Ed25519로 canonical JCS 바이트에 서명하고, signature 객체는 서명된 바이트 외부에 있음)으로 추가합니다. 명명된 승인자가 실행 전에 전체 정규화 행동 및 그 다이제스트에 서명하며, 에이전트 행동 영수증은 같은 행동 다이제스트와 parent_approval_ref 승인 영수증 해시를 포함합니다. verify_chain 함수는 두 아티팩트를 별도의 고정 키 레지스트리 하에 검증하는데(승인자 키 vs 에이전트 키), 코드 경로는 공유하지만 권한자는 분리됩니다.

이 속성의 핵심: 사람이 이 정확한 행동을 승인했고, 에이전트가 바르게 실행했다. 거절 사례 테스트가 이 속성을 주장하지 않고 실제로 보장합니다:

각 실패는 고유한 사유로 거부하여 감사관이 권한 만료인지 실행 행동 변경인지 알 수 있게 합니다. 노트북 규칙: 서명된 승인만으로 권한 인정은 안 됨. 권한은 두 영수증이 실행 시 동일 정규화 행동에 묶여 있을 때만 존재. 인간 승인 영수증은 이 수업에서 정의한 교육용 조합이며, draft-farley-acta-signed-receipts에 정의된 영수증 종류는 아닙니다.

프로덕션 참고자료

이 수업의 Python 코드는 일부러 최소화되어 있어 모든 줄을 읽고 정확히 무슨 일이 일어나는지 알 수 있습니다. 프로덕션에서는 두 가지 선택지가 있습니다:

  1. 암호학 기본 도구에 직접 구현하기. 위 50줄 코드는 많은 용도에 충분합니다. PyNaCl(Ed25519)과 jcs 패키지(정규화 JSON)는 잘 관리되고 감사된 라이브러리입니다.

  2. 프로덕션 영수증 라이브러리 사용하기. 여럿 오픈소스 프로젝트가 키 교체, 배치 검증, JWK 세트 배포, 정책 엔진 통합 등 부가 기능과 함께 같은 패턴을 구현합니다:

    • 서명 파이프라인은 독립 IETF 인터넷 초안(draft-farley-acta-signed-receipts, 개정 02)의 JCS 및 서명 범위 규칙을 사용합니다. 이 수업의 단순 교육용 영수증은 초안의 {payload, signature} 형식과 다르며, 준수 구현으로 제시되지 않습니다. 초안은 자체 와이어 형식 대상 구현을 위한 공유 적합성 테스트 벡터(agent-governance-testvectors)를 공개합니다.
    • Microsoft Agent Governance Toolkit은 Cedar 기반 정책 결정과 영수증을 조합합니다; 해당 저장소의 Tutorial 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 필드를 더 관대한 정책으로 주장하도록 수정했습니다. 서명은 원본 페이로드 위에 되어 있습니다. 검증 시 어떻게 됩니까?

답변 검증에 실패했습니다. 서명은 원본 페이로드의 표준 바이트에 대해 계산되었으며, 어떤 필드를 변경하면 해당 바이트가 변경되어 서명이 무효가 됩니다. 공격자는 새로 유효한 서명을 만들기 위해 개인 키가 필요하지만, 이를 가지고 있지 않습니다.

3. 영수증에 원시 인수와 결과 대신 tool_args_hash 및 result_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 에이전트 프레임워크로 구축된 에이전트에 이 패턴을 적용합니다: 도구 호출을 영수증 서명으로 감싸고 영수증을 독립적으로 검증합니다.

도전 과제 1: 직접 선택한 추가 필드(예: 추적용 요청 ID)로 영수증 스키마를 확장하고 표준 서명 로직에 포함시키고 검증이 통과하는지 확인하세요. 그런 다음 서명 후 필드를 변경하고 검증 실패를 확인하세요. 이는 표준 인코딩의 모든 바이트가 서명에 어떻게 기여하는지 이해하는 데 도움이 됩니다.

도전 과제 2: 두 개의 영수증을 SHA-256 해시로 결합(표준 바이트를 결정적 순서로 연결)하고, 결과 해시를 세 번째 영수증의 새 필드에 삽입하여 서명하세요. 세 개의 영수증 모두가 계속 검증될 수 있음을 확인하세요. 이는 한 단계 포함 증명입니다: 세 번째 영수증 소지자는 첫 두 영수증이 서명 시점에 존재함을 내용 노출 없이 증명할 수 있습니다. 이는 선택적 공개 영수증이 대규모로 활용하는 패턴입니다(Merkle 커밋, RFC 6962).

결론

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

이들은 입력 검증, 정책 집행, 신원 인프라를 대체하지 않습니다. 오히려 그런 계층을 위한 기반입니다. 규제된 작업 부하, 다기관 워크플로우, 또는 미래 감사자가 신뢰를 전제로 할 수 없는 환경에서 에이전트를 배포할 때, 영수증은 감사 추적의 정직성을 보장하는 방법입니다.

가장 중요한 요점: 영수증은 누가 언제 무엇을 말했는지 증명합니다. 그것이 진실하거나 옳았다는 것을 증명하지는 않습니다. 이 차이를 분명히 유지하세요. 이는 정직한 출처 시스템과 오해를 불러오는 시스템의 차이입니다.

운영 체크리스트

이 수업에서 실제 환경으로 영수증 서명 에이전트를 배포할 준비가 되었을 때:

AI 에이전트 보안에 관한 추가 질문이 있나요?

Microsoft Foundry Discord에 참여하여 다른 학습자들과 만나고, 오피스 아워에 참석해 AI 에이전트 질문에 답을 받으세요.

이 수업을 넘어서

이 수업은 단일 영수증 서명과 해시 체인 시퀀스를 다룹니다. 동일한 프리미티브들은 거버넌스가 성숙됨에 따라 만나게 될 여러 고급 패턴을 구성합니다:

추가 자료

이전 수업

로컬 AI 에이전트 만들기


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