본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 15. 13:25

AI 결정 증거를 위한 오프라인 검증기 구축하기

요약

AI 결정의 신뢰성을 확보하기 위해 서명 및 타임스탬프가 포함된 증거 기록을 검증하는 AURORA 오프라인 검증기 구축 사례를 소개합니다. 특정 벤더에 종속되지 않고 암호화 자료를 통해 독립적으로 증거를 검증하는 설계 원칙을 다룹니다.

핵심 포인트

  • AI 결정의 독립적 검증을 위한 오프라인 검증기(Offline Verifier) 구현
  • 특정 제공업체에 종속되지 않는 벤더 중립적(Provider-neutral) 설계
  • RSA 서명, 해시 체인, RFC 3161 타임스탬프를 활용한 무결성 검증
  • 별도의 도구 없이 브라우저만으로 증거 번들을 검증하는 사용자 경험

AI 결정 증거를 위한 오프라인 검증기(Offline Verifier) 구축하기

대부분의 감사 로그(audit-log) 시스템에는 내재된 조용한 의존성이 있습니다:

로그를 믿으려면, 그 로그를 작성한 시스템을 믿어야 한다는 점입니다.

클라우드 제공업체가 자신의 로그를 봉인(sealing)하더라도, 그것은 여전히 클라우드 제공업체가 스스로를 보증하는 것과 같습니다. SaaS 플랫폼이 자체 감사 추적(audit trail)을 내보내는 것도 결국 검토자에게 증거를 생성한 플랫폼을 신뢰할 것을 요구하는 것입니다.

그것이 항상 틀린 것은 아닙니다. 하지만 독립적이지는 않습니다.

증거를 확인할 수 있는 유일한 방법이 그것을 생성한 당사자를 신뢰하는 것뿐이라면, 그 증거는 구조적인 한계를 가집니다.

저는 AI 결정을 서명되고 타임스탬프가 찍힌 증거 기록으로 봉인하는 시스템인 AURORA를 구축해 왔습니다.

이번 주에 저는 _독립적(independent)_이라는 단어를 더 실질적으로 만드는 부분을 출시했습니다:

바로 오프라인 검증기(offline verifier)입니다.

이제 검토자는 AURORA 증거 번들(evidence bundle)을 가져와서 .zip 파일을 브라우저에 넣기만 하면, 그 안에 포함된 암호화 자료(cryptographic material)를 바탕으로 번들을 검증할 수 있습니다.

OpenSSL도 필요 없습니다.

셸(shell)도 필요 없습니다.

계정도 필요 없습니다.

저를 신뢰할 필요도 없습니다.

검증기는 매니페스트(manifest), 파일 해시 인벤토리(file-hash inventory), RSA 서명(RSA signature), 레코드 해시 체인(record hash chain), 그리고 RFC 3161 타임스탬프 증거를 확인합니다. 각 결과는 고유한 범위 경계(scope boundary)와 함께 표시됩니다.

이를 구축하며 배운 점은 다음과 같습니다.

1. 검증기는 제공업체 중립적(provider-neutral)이어야 한다

첫 번째 중요한 설계 결정은 UI에 관한 것이 아니었습니다.

검증기가 실제로 무엇을 검증해야 하는가에 관한 것이었습니다.

단순한 검증기는 다음과 같이 벤더(vendor)를 확인합니다:

이것이 유효한 GlobalSign 타임스탬프인가?

또는:

이것이 유효한 FreeTSA 토큰인가?

처음에는 합리적으로 들릴 수 있습니다. 하지만 이는 검증 로직을 특정 제공업체에 결합(couple)시킵니다.

타임스탬프 제공업체를 변경하거나, 두 번째 제공업체를 추가하거나, 기업 고객이 자체 TSA를 가져오게 되는 순간, 당신의 검증기는 증거 검증기가 아닌 제공업체 어댑터(provider adapter)가 되어 버립니다.

그것은 제가 원했던 형태가 아니었습니다.

따라서 검증기는 벤더가 아니라 **번들 계약(bundle contract)**을 확인합니다.

검증기는 다음을 확인합니다:

  • 정규 기록(canonical record) 바이트가 기록된 data_hash로 해싱되는지
  • RSA 서명이 번들링된 공개 키(public key)를 사용하여 정규 JSON(canonical JSON)에 대해 검증되는지
  • 공개 키 지문(public key fingerprint)이 게시된 지문과 일치하는지
  • 필요한 자료가 존재하는 경우 기록 해시 체인(record hash chain)이 자기 일관성(self-consistent)을 유지하는지
  • RFC 3161 타임스탬프 토큰 각인(timestamp token imprint)이 타임스탬프가 찍힌 메시지에 결합되는지
  • 번들 파일 인벤토리(bundle file inventory)가 기록된 SHA-256 해시와 일치하는지

이러한 검사 중 그 어느 것도 검증기가 어떤 TSA(타임스탬프 권한 부여 기관) 벤더가 타임스탬프를 발행했는지 신경 쓸 것을 요구하지 않습니다.

무료 TSA에 의해 타임스탬프가 찍힌 번들과 적격(qualified) TSA에 의해 타임스탬프가 찍힌 번들은 동일한 검증 모델을 통해 처리되어야 합니다.

신뢰 계층(trust layer)은 변할 수 있습니다.

하지만 검증 계약(verification contract)은 변해서는 안 됩니다.

그 분리가 중요합니다.

만약 검증기가 단일 제공업체에 종속된다면, 증거 형식(evidence format)은 해당 제공업체 관계의 생명주기를 상속받게 됩니다. 반면 검증기가 번들 계약에 종속된다면, 증거 형식은 제공업체의 변경 이후에도 생존할 수 있습니다.

그것이 제가 가장 먼저 원했던 속성입니다.

2. 하나의 코어, 두 명의 소비자, 숨겨진 앱 의존성 없음

검증기에는 두 명의 소비자가 있습니다.

첫 번째는 독립형 CLI(Command Line Interface)입니다. 이는 AURORA 외부에서 완전히 검증을 실행하고자 하는 감사인(auditors), 엔지니어 또는 검토자를 위한 것입니다.

두 번째는 백엔드 /verify/bundle 엔드포인트입니다. 이는 브라우저 기반의 업로드 흐름을 위한 것입니다.

당연한 선택은 검증기 코어를 하나의 공유 패키지에 넣고 어디에서나 임포트(import)하는 것이었을 것입니다.

하지만 저는 그렇게 하지 않았습니다.

독립형 검증기는 의존성을 최소화(dependency-minimal)한 상태를 유지해야 합니다. 외부 검토자가 단지 서명을 확인하기 위해 FastAPI 앱, 데이터베이스 클라이언트, 인증 계층(auth layer), 스토리지 SDK 또는 내부 AURORA 패키지를 설치할 필요가 있어서는 안 됩니다.

따라서 코어는 순수 라이브러리(pure library)입니다:

바이트(bytes) 입력.

구조화된 결과(structured result) 출력.

데이터베이스 없음.

서비스 임포트 없음.

app.crypto 없음.

TSA 클라이언트 임포트 없음.

HTTP 가정 없음.

CLI는 경로를 읽고, 번들을 로드하며, 코어를 호출한 뒤, 텍스트 또는 JSON을 렌더링합니다.

백엔드는 업로드된 .zip 파일을 수신하고, 동일한 코어 (core)를 호출한 뒤, 구조화된 결과를 반환합니다.

두 복사본을 일치시키기 위해, 동기화 스크립트 (sync script)를 통해 검증기 코어 (verifier core)가 백엔드에 벤더링 (vendored)됩니다. 이 스크립트는 소스 복사본의 SHA-256 값을 찍어두며, CI는 --check 모드를 실행하여 백엔드 복사본이 독립형 검증기 (standalone verifier)와 달라질 경우 빌드를 실패하게 할 수 있습니다.

제품의 목표를 떠올리기 전까지는 이것이 중복처럼 느껴질 수 있습니다.

핵심은 사람들에게 AURORA를 필요 이상으로 신뢰하라고 요구하지 않는 것입니다.

AURORA의 앱 트리 (app tree) 내부에서만 작동하는 검증기는 독립적인 검증기가 아닙니다. 그것은 그저 또 다른 내부 엔드포인트 (endpoint)일 뿐입니다.

독립형 경로 (standalone path)는 반드시 실재해야 합니다.

3. 나에게 가장 많은 것을 가르쳐준 버그: 과적합(overfit)하지 말고 정규화(normalize)하라

첫 번째 회귀 (regression)는 매우 작은 실수였지만 매우 큰 영향을 미치는 문제를 포착했습니다.

나는 번들 형식 (bundle format)에 대한 매니페스트 (manifest) 확인 절차를 추가했습니다.

검증기에는 허용된 형식 문자열의 화이트리스트 (whitelist)가 있었습니다.

나의 테스트 피스처 (test fixture)는 통과했습니다.

하지만 실제 프로덕션 번들은 실패했습니다.

이유는 암호학적인 것이 아니었습니다. 서명 (signature)이 깨진 것도 아니었고, 번들이 잘못 형성된 것도 아니었습니다.

그것은 대소문자 (casing) 문제였습니다.

프로덕션에서는 다음을 사용했습니다:

AURORA_Verification_Bundle

검증기는 약간 다른 대소문자 및 구분자 (separator) 컨벤션을 기대하고 있었습니다.

번들은 유효했습니다.

나의 일치 확인 (equality check) 로직이 너무 취약 (brittle)했습니다.

해결책은 비교하기 전에 정규화 (normalize)하는 것이었습니다:

  • trim (공백 제거)
  • lowercase (소문자화)
  • 구분자 정규화 (normalize separators)
  • 그 후 정준 형식 식별자 (canonical format identity)와 비교

교훈은 간단합니다:

양쪽 모두에서 제어할 수 있는 식별자 (identifiers)를 검증할 때는, 먼저 정규화하십시오.

정확한 문자열 화이트리스트 (string whitelists)는 프로토콜 경계 (protocol boundaries)에서 유용합니다. 하지만 시스템 자체에 이미 기존의 또는 프로덕션의 변형 (variants)이 존재할 때, 엄격한 리터럴 체크 (literal check)는 유효한 증거를 잘못된 실패 (false failure)로 바꿀 수 있습니다.

검증기에서의 거짓 음성 (false negatives)은 위험합니다.

감사관이 정당한 증거 번들 (evidence bundle)을 업로드했는데, 내부 라벨 하나가 다른 언더스코어 (underscore) 패턴을 사용했다는 이유로 도구가 FAILED라고 말하는 상황을 상상해 보십시오.

그것은 무결성 (integrity)이 아닙니다.

그것은 당신의 검증기 (validator)가 형식을 증거로 착각한 것입니다.

4. 실패 모드 (Failure modes)는 정직해야 합니다

이 부분이 제가 가장 중요하게 생각하는 지점입니다.

검증기 (verifier)가 권력을 갖는 이유는 판결 (verdict)을 내리기 때문입니다.

그것은 또한 검증기를 위험하게 만듭니다.

만약 검증기가 모호함을 숨기거나, 경고 (warning)를 통과 (pass)로 반올림하거나, 실제로 증명하지 않은 신뢰 주장을 한다면, 그것은 검토자 (reviewer)가 맹목적으로 믿어야 하는 또 다른 시스템이 되어버립니다.

그것은 목적에 어긋납니다.

따라서 검증기는 필수 체크 항목 (required checks), 경고 (warnings), 사용 불가능한 체크 항목 (unavailable checks), 그리고 해당 사항 없는 체크 항목 (non-applicable checks)을 분리합니다.

예를 들어, AURORA는 현재 일부 번들 (bundles)에서 이중 경로 타임스탬프 조건 (dual-path timestamp condition)을 가지고 있습니다.

봉인 (sealing) 시점의 타임스탬프 해시 바운드 (timestamp hash bound)와 번들링된 타임스탬프 토큰의 직접적인 해시는 서로 다를 수 있는데, 이는 번들링된 토큰이 별도의 TSA (Timestamp Authority) 호출로부터 올 수 있기 때문입니다.

단순히 기계적으로 바이트를 비교하기만 한다면 이것은 의심스러워 보일 수 있습니다.

하지만 이것이 반드시 변조 (tampering) 신호인 것은 아닙니다.

그래서 검증기는 이를 평이한 언어로 된 설명과 함께 경고 (warning)로 보고합니다. 필수 서명 (signature), 해시 (hash), 매니페스트 (manifest), 그리고 레코드 체인 (record-chain) 체크를 통과한다면 무결성 (integrity) 판결은 여전히 통과 (pass)될 수 있습니다.

타임스탬프 신뢰 상태 (timestamp trust status)도 마찬가지입니다.

만약 TSA가 비적격 (non-qualified) 상태라면, 결과는 non_qualified라고 표시됩니다.

만약 적격한 신뢰 목록 검증 (qualified trust-list validation)이 수행되지 않았다면, 검증기는 수행된 척하지 않습니다.

그 경계는 결과의 일부입니다.

숨겨지지 않습니다.

꾸며지지 않습니다.

조용히 격상되지도 않습니다.

원칙은 다음과 같습니다:

무엇을 체크했는지, 무엇이 통과했는지, 무엇이 실패했는지, 그리고 무엇이 증명되지 않았는지를 정확하게 말하십시오.

당연한 소리처럼 들릴 수도 있습니다.

하지만 많은 소프트웨어가 그렇게 동작하지는 않습니다.

자신의 한계를 숨기는 검증기는 그저 또 다른 신뢰 요구일 뿐입니다.

5. HTTP 의미론 (semantics)은 보이는 것보다 더 중요합니다

브라우저 검증기는 작지만 중요한 또 다른 구분을 드러냈습니다:

잘못된 형식의 업로드 (malformed upload)와 검증 실패 (failed verification)는 같은 것이 아닙니다.

만약 누군가 무작위 파일을 업로드하거나, 매니페스트 (manifest)가 없는 .zip 파일을 업로드한다면, 서버는 번들 검증 (bundle verification)을 수행할 수 없습니다.

그것은 구조적인 입력 문제 (structural input problem)입니다.

따라서 다음과 같은 값을 반환해야 합니다:

422 Unprocessable Entity

하지만 누군가가 형식이 올바른 AURORA 번들 (bundle)을 업로드했는데 RSA 서명 (signature) 검증에 실패했다면, 그것은 다른 문제입니다.

검증기 (verifier)는 성공적으로 실행되었습니다.

단지 결과가 '아니오'일 뿐입니다.

이 경우 다음과 같이 반환해야 합니다:

200 OK
{
  "ok": false,
...

암호화 검증 (cryptographic check) 실패는 서버 오류가 아닙니다.

그것은 유효한 검증 결과입니다.

두 케이스를 모두 일반적인 400 오류로 통합해버리면 다음과 같은 차이점이 사라집니다:

이 증거를 읽을 수 없습니다.

와:

이 증거를 읽었으나, 검증되지 않습니다.

감사자 (auditors)에게는 그 차이가 중요합니다.

디버깅 (debugging)을 위해서도 중요합니다.

신뢰를 위해서도 중요합니다.

6. 브라우저는 편의성입니다. CLI는 독립성입니다.

새로운 브라우저 검증기 (browser verifier)는 마찰을 제거해주기 때문에 유용합니다.

검토자 (reviewer)는 OpenSSL을 설치할 필요가 없습니다. 어떤 명령어가 서명 (signature)을 검증하는지, 또는 RFC 3161 토큰 (token)을 어떻게 파싱 (parse)하는지 알 필요도 없습니다.

그들은 번들 (bundle)을 업로드하고 다음 항목들을 확인할 수 있습니다:

  • 매니페스트 (manifest)
  • 파일 인벤토리 (file inventory)
  • 정규 레코드 해시 (canonical record hash)
  • 서명 (signature)
  • 레코드 해시 체인 (record hash chain)
  • 타임스탬프 증거 (timestamp evidence)

이것이 접근성이 높은 경로입니다.

하지만 이것이 유일한 경로는 아닙니다.

CLI는 완전히 독립적인 경로이기 때문에 여전히 중요합니다.

브라우저 흐름 (flow)은 여전히 AURORA의 서버에 검증기 코어 (verifier core)를 실행하도록 요청합니다. 이는 유용하지만, 최대한의 독립성을 갖춘 것은 아닙니다.

CLI를 사용하면 검토자가 AURORA의 인프라 (infrastructure) 외부에서 동일한 검증 로직 (verification logic)을 실행할 수 있습니다.

그렇기 때문에 검증 가이드에는 이제 두 가지 방식이 모두 포함되어 있습니다:

  • CLI 없이 검토하기 위한 브라우저 기반 번들 검증 (browser-based bundle verification)
  • 완전한 독립적 오프라인 검증을 위한 OpenSSL / CLI 검증 (OpenSSL / CLI verification)

브라우저는 장벽을 낮춥니다.

CLI는 신뢰 경계 (trust boundary)를 보존합니다.

둘 다 중요합니다.

7. 검증기가 수행하지 않는 것

검증기는 AI 결정이 옳았다고 말해주지 않습니다.

검증기는 그 결정이 적법했다고 말해주지 않습니다.

검증기는 그 결정이 공정했다고 말해주지 않습니다.

검증기는 규제 기관 (regulator)이 이를 승인했다고 말해주지 않습니다.

검증기는 법원이 이를 증거로 채택할 것이라고 말해주지 않습니다.

그것은 검증기의 역할이 아닙니다.

검증기의 역할은 더 좁습니다:

  • 이 증거 패키지(evidence package)가 온전한가?
  • 해시(hashes)가 일치하는가?
  • 서명(signature)이 검증되는가?
  • 타임스탬프(timestamp) 증거가 예상되는 메시지에 결합되어 있는가?
  • 외부 검토자가 무결성 검사(integrity checks)를 재현할 수 있는가?
  • 결과값이 자체적인 한계를 명확히 명시하고 있는가?

그러한 좁은 범위 설정은 의도적인 것입니다.

AURORA는 판사가 되려고 하는 것이 아닙니다.

그것은 보관 계층(custody layer)이 되고자 하는 것입니다.

구현 결과

결과물이 이제 공개되었습니다:

aurora-audit.com/verify-bundle

계정도 없고, CLI도 없으며, 저를 신뢰할 이유가 없는 외부 검토자가 AURORA 증거 번들(evidence bundle)을 브라우저로 드래그하여 검증기가 패키지를 확인하는 과정을 지켜볼 수 있습니다.

핵심 기능은 아직 베타 단계입니다.

다듬어지지 않은 부분들이 여전히 보입니다.

자격이 있는 타임스탬프 신뢰 목록(timestamp trust-list) 검증은 별도의 신뢰 계층(trust layer)이며 여전히 강화 작업이 진행 중입니다. 검증기는 그렇지 않은 척하지 않습니다.

하지만 중요한 부분은 이제 실재합니다:

증거가 원래의 워크플로(workflow)를 벗어나더라도 여전히 확인될 수 있다는 점입니다.

그것이 제가 가장 먼저 넘고자 했던 경계였습니다.

만약 여러분이 독립적으로 검증 가능한 증거 시스템을 구축했다면, 여러분은 신뢰 경계(trust-boundary) 문제를 어떻게 다루었는지 궁금합니다:

“검증(verify)”과 “나를 믿으라(trust me)” 사이의 선을 어디에 그으셨나요?

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0