본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 25. 23:04

AgentAuth 심층 분석: 소스 코드로 살펴보는 AI 에이전트를 위한 자기 인증 UUID (Self-Authenticating UUID)

요약

AI 에이전트의 식별 문제를 해결하기 위한 AgentAuth의 자기 인증 UUID(Self-Authenticating UUID) 메커니즘을 소스 코드 수준에서 분석합니다. 기존의 인간 중심적 인증 방식 대신, 토큰으로부터 ID와 주소를 결정론적으로 유도하고 서명을 통해 검증하는 방식을 제안합니다.

핵심 포인트

  • 에이전트 환경에서는 기존의 UI 기반 로그인/OAuth 방식이 작동하지 않음
  • AgentAuth는 단일 UUID를 통해 신원 확인과 인증을 동시에 수행
  • 토큰으로부터 Address와 ID를 결정론적으로 유도하는 구조
  • 모든 요청에 서명을 첨부하여 실제 소유자 여부를 서버에서 검증

트리거: 에이전트에게 로그인 화면을 보여주는 것은 의미가 없습니다

제가 MCP (Model Context Protocol) 서버를 작성할 때마다 동일한 문제가 저를 가로막습니다. 방금 이 요청을 보낸 에이전트는 누구이며, 제가 그것을 어떻게 식별해야 할까요?

사람을 대상으로 하는 웹 서비스의 경우 답은 정해져 있습니다. 로그인 양식을 보여주고, 이메일과 비밀번호를 받은 뒤, 세션 쿠키 (session cookie)를 돌려줍니다. 또는 OAuth를 사용하여 브라우저를 IdP (Identity Provider)로 리다이렉트하고, 동의 화면을 보여준 뒤, 토큰 (token)을 가지고 돌아오게 합니다. 이 모든 방식은 화면 앞에 사람이 앉아 있음을 전제로 합니다.

AI 에이전트 앞에는 아무도 앉아 있지 않습니다. 에이전트는 새벽 3시에 스스로 도구 (tools)를 실행하며, 한 명의 사용자가 동시에 10개의 에이전트를 실행할 수도 있습니다. "동의 화면을 클릭하세요"나 "비밀번호를 기억하세요"라는 말은 모두 성립하지 않습니다. 그렇다고 모든 에이전트에게 동일한 API 키를 부여하면, 이제 누가 무엇을 했는지 구분할 수 없게 됩니다.

AgentAuth는 상당히 극단적인 방식으로 이 문제에 답합니다.

당신에게 필요한 것은 단 하나의 UUID뿐입니다. 로그인도, 세션도, 추가적인 인프라도 필요 없습니다.

저의 첫 반응은 "UUID로 인증을 한다고요? 그건 그냥 ID 아닌가요?"였습니다. 하지만 소스 코드를 읽어보니, 이것은 "그냥 ID"가 아니었습니다. 이것은 스스로의 정확성을 증명할 수 있는 ID입니다. 이 글에서는 그 메커니즘을 코드 수준까지 파헤쳐 보겠습니다.

먼저 전체 그림을 다이어그램으로 보여드리겠습니다. 그 후 하나씩 분해하여 살펴보겠습니다.

Self-authenticating UUID overview: Token derives Address and ID, and a per-request signature is verified by the server

오직 Token만이 비밀입니다. AddressID는 그것으로부터 결정론적 (deterministically)으로 유도됩니다. 모든 요청에 Signature (서명)를 첨부함으로써, 서버는 해당 ID의 실제 소유자가 실제로 요청을 보냈는지 확인할 수 있습니다. 이것이 바로 "자기 인증 UUID (self-authenticating UUID)"가 의미하는 바입니다.

배경: 신원 (identity)과 인증 (authentication)은 서로 다른 두 가지입니다

메커니즘을 설명하기 전에, 용어 한 쌍을 명확히 짚고 넘어가겠습니다. 이 개념이 모호하게 남는다면 AgentAuth의 흥미로운 지점을 결코 이해할 수 없을 것입니다.

  • 신원 (Identity): "당신은 누구인가"에 대한 것입니다. 사용자 ID나 사번과 같습니다. 단순히 사람들을 구별하기만 하면 됩니다.
  • 인증 (Authentication): "당신이 정말 당신인가"에 대한 것입니다. 비밀번호나 지문과 같습니다. 사칭을 방지합니다.

일반적인 시스템에서 이 두 가지는 서로 다른 장치로 구축됩니다. 신원은 "DB에 있는 사용자 ID"이고, 인증은 "비밀번호 확인"입니다. 이것이 바로 등록(신원 생성)과 로그인(인증)이라는 두 단계가 필요한 정확한 이유입니다.

AgentAuth의 주장은 **"이 두 가지를 하나의 UUID로 통합할 수 있다"**는 것입니다. UUID를 보면 그것이 누구인지 알 수 있고(신원), 해당 UUID의 소유자만이 생성할 수 있는 서명 (signature)을 첨부하면 인증도 가능해집니다. 등록과 로그인 단계가 모두 사라지는 것입니다.

이것이 어떻게 가능할까요? 핵심은 공개키 암호학 (public-key cryptography)입니다. 앞으로 이 개념이 계속해서 등장할 것이므로, 우선 최소한의 세 가지 속성을 먼저 정리하겠습니다. 이미 알고 계신다면 건너뛰셔도 좋습니다.

  • 키는 쌍으로 존재한다: "개인키 (private key)"와 "공개키 (public key)"는 항상 함께 생성됩니다. 개인키는 본인만이 보유하며, 공개키는 누구에게나 보여줄 수 있습니다.
  • 단방향성 (One-way): 개인키로부터 공개키를 계산하는 것은 쉽습니다. 그 반대(공개키로부터 개인키를 복구하는 것)는 사실상 불가능합니다. 언덕을 내려가는 것과 다시 올라가는 것의 차이를 상상해 보세요.
  • 개인키로만 서명할 수 있고, 누구나 공개키로 검증할 수 있다: 개인키를 사용하여 데이터에 "서명 (signature)"을 첨부하면, 일치하는 공개키를 가진 사람은 누구나 "이 개인키의 소유자가 이 데이터에 대해 이 서명을 만들었다"는 것을 확인할 수 있습니다. 개인키가 없는 사람은 유효한 서명을 위조할 수 없습니다.

AgentAuth는 이 세 가지 속성을 "신원 (단방향 유도)"과 "인증 (서명)"에 그대로 매핑합니다. 순서대로 살펴보겠습니다.

핵심 개념: 토큰에서 주소로, 그리고 ID로 이어지는 단방향 경사면

AgentAuth에는 세 가지 값이 있습니다. 이들은 서로 혼동하기 쉬우므로, 먼저 각 역할과 "비밀 여부"를 표로 정리해 보겠습니다.

이름예시내용비밀 여부?역할
AgentAuth Tokenaa-2337b9fa...bcb602secp256k1 개인키 (32 bytes)모든 것의 근원. 비밀번호와 동일한 역할
...

핵심은 이 세 가지가 Token에서 Address로, 그리고 ID로 이어지는 순서에 따라 "단방향"으로 유도된다는 점입니다. 경사면을 내려가는 것은 계산 비용이 저렴하지만, 다시 올라가는 것(ID에서 Token을 복구하는 것)은 암호학적으로 불가능합니다.

Derivation chain: Token to public key to Address to AgentAuth ID, with the Token never leaving the client

이 점에 주목하세요: Address와 ID만이 서버에 도달하며, Token은 단 한 번도 외부로 유출되지 않습니다. 사용자는 개인키 (private key)를 직접 소유하고, 그것으로부터 유도된 공개 값 (public values)만을 상대방에게 보여줍니다. 이는 나중에 "서명 (signature)"과 결합될 때 큰 효과를 발휘합니다.

실제 코드(@agentauth/core)를 통해 세 단계를 하나씩 살펴보겠습니다.

단계 1 및 2: Token으로부터 Address 유도하기

deriveAddress가 수행하는 작업은 사실 Ethereum의 주소 유도 방식과 정확히 동일합니다.

export function deriveAddress(privateKey: string): string {
  // "aa-" 또는 "0x"를 제거하여 32바이트 개인키로 만듭니다
  const cleanPrivateKey = parsePrivateKey(privateKey);
...

secp256k1 (타원 곡선)을 사용하여 공개키 (public key)를 계산하고, 이를 keccak256으로 해싱한 뒤 마지막 20바이트를 취합니다. 결과로 나오는 0x... 형식은 Ethereum 지갑 주소와 동일한 형식입니다.

오해해서는 안 될 한 가지가 있습니다. 블록체인과 관련된 비트(bit)는 단 하나도 없습니다. 온체인 처리나 가스비(gas)도 없습니다. 이 방식은 단순히 암호 라이브러리 수준에서 Ethereum이 수년 동안 '개인 키로부터 주소를 도출하는' 데 사용해 온 검증된 레시피를 재사용할 뿐입니다. 구현은 감사된 @noble/secp256k1@noble/hashes에 의존합니다.

3단계: 주소로부터 안정적인 UUID 생성하기

generateId 함수는 매우 간결합니다.

const AGENTAUTH_NAMESPACE = '2f5a5c48-c283-4231-8975-9271fe11e86c';

export function generateId(address: string): string {
...

UUIDv5는

지금까지 우리는 신원(ID)을 살펴보았습니다. 하지만 ID는 공개된 정보입니다. 만약 누군가 당신의 AgentAuth ID를 알게 된다면, 그저 그것을 사칭하여 요청을 날릴 수 있지 않을까요?

이를 방지하는 핵심 기술은 바로 서명 (signature) 입니다. 서명은 AgentAuth ID의 진정한 소유자, 즉 토큰 (Token)을 보유한 사람만이 생성할 수 있는 증거를 요청에 첨부합니다.

서명 생성 (클라이언트 측)

export function signPayload(payload: object, privateKey: string): string {
  const messageString = JSON.stringify(payload);
  const messageHash = keccak_256(messageString);
...

페이로드 (payload, JSON)를 keccak256으로 해싱 (Hash)하고 개인키 (private key)로 서명합니다. 출력값은 0x와 65바이트의 서명으로 구성됩니다. 아래에 언급된 바와 같이, 페이로드에는 항상 타임스탬프 (timestamp) 가 포함됩니다.

서명 검증 (서버 측)

검증 측면에서의 핵심은 "서명으로부터 공개키를 복구 (recover) 하는" 작업입니다. 이는 Ethereum의 ecrecover와 동일한 개념입니다. 즉, 서명과 메시지를 바탕으로 "이 서명을 생성했을 수 있는 개인키에 대응하는 주소"를 역산하여 계산합니다.

export function verifySignature(
  signature: string, payload: object, expectedAddress: string
): boolean {
...

이를 말로 풀어서 설명하면 다음과 같습니다.

  1. 클라이언트가 "나는 주소 0x9906... 이다"라고 주장합니다.
  2. 동시에 페이로드에 대한 서명을 첨부합니다.
  3. 서버는 서명으로부터 "이 서명을 만든 사람의 주소"를 복구합니다.
  4. 복구된 주소가 주장된 주소와 일치하면, 해당 요청은 정당한 것으로 간주됩니다.

개인키가 없는 제3자는 3단계에서 올바른 주소로 복구되는 서명을 생성할 수 없습니다. 따라서 "단순히 ID를 아는 것"만으로는 통과할 수 없습니다. 이것이 바로 ID가 공개되어 있음에도 인증이 유지되는 이유입니다.

요청의 전체 왕복 과정 (Round Trip)

@agentauth/sdkverify()는 서버 측의 진입점 (entry point) 입니다. 요청에는 세 개의 HTTP 헤더가 포함됩니다.

헤더 (Header)내용 (Contents)
x-agentauth-address주장된 주소 (The claimed Address, 0x...)
...

하나의 교환을 시퀀스 (sequence)로 따라가면 다음과 같습니다. 색상 띠는 클라이언트 측 서명 (client-side signing, 빨간색), 서버 측 검증 (server-side verification, 파란색), 성공 (success, 초록색), 그리고 실패 (failure, 회색)를 나타냅니다.

One request round trip: the client signs, the server runs verify() with four stateless checks, then branches on success or failure

verify() 코드는 이 네 단계를 직접적으로 작성한 것입니다.

export function verify(request, options = {}): VerificationResult {
  const freshness = options.freshness ?? 60000; // 기본값 60초

...

눈에 띄는 점은 서버가 어떠한 상태도 유지하지 않고 (without holding any state at all) (세션 없음, 기록된 논스 (nonce) 없음) 검증을 완료한다는 것입니다. DB 조회도, 캐시 (cache)도 없습니다. 오직 서명 (signature)과 타임스탬프 (timestamp)만으로 결정합니다. 이것이 바로

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0