시크릿, 페어링, 디바이스 인증
암호화된 시크릿 저장소, 디바이스 페어링 및 베어러 토큰, 서비스 토큰, 그리고 민감 값 난독화에 대한 설명입니다.
Revka는 자격 증명을 평문으로 보관하지 않습니다. 설정 파일에 입력된 API 키와 토큰은 디스크에 저장되기 전에 인증 암호화로 봉인되고, 디바이스는 해시로만 보관되는 베어러 토큰으로 인증하며, 신뢰할 수 있는 로컬 사이드카는 별도의 서비스 토큰을 사용합니다. 로그에 기록되는 값은 난독화 헬퍼에 의해 잘려 출력됩니다. 이 페이지에서는 ChaCha20-Poly1305 시크릿 저장소, [secrets] 설정 키, 디바이스 페어링 가드와 베어러 토큰, 서비스 토큰, redact() 헬퍼, 그리고 레거시 enc: 포맷에서 enc2:로의 단방향 마이그레이션에 이르는 암호화 설계 전반을 설명합니다.
디스크 위의 시크릿 보호 방식을 이해하거나, 암호화 키를 복구·백업하거나, 디바이스 인증을 강화하거나, 구형 암호화 값을 마이그레이션하려는 경우 이 페이지를 참고하세요. REST 페어링 엔드포인트와 디바이스 관리 API(요청/응답 형식, 디바이스 레지스트리, 키 교체)는 페어링 및 인증을 참고하세요. 전체 레이어의 설계 근거는 보안 모델을 참고하세요.
암호화된 시크릿 저장소
섹션 제목: “암호화된 시크릿 저장소”설정에 기록하는 시크릿 — 프로바이더 API 키, 채널 토큰, 웹훅 시크릿 — 은 저장 전에 ChaCha20-Poly1305 AEAD로 암호화됩니다. 이는 심층 방어의 일환으로, 설정 파일에는 16진수로 인코딩된 암호문만 저장되어 grep, git log, 또는 우발적인 커밋으로도 원시 키가 유출되지 않습니다.
동작 방식:
- OS CSPRNG에서 생성한 32바이트(256비트) 난수 키가
~/.revka/.secret_key에 16진수로 저장됩니다. 값이 처음 암호화될 때 자동으로 생성됩니다. - 각 암호화 시 신선한 12바이트 난수 논스가 생성되므로, 동일한 평문을 두 번 암호화해도 서로 다른 암호문이 생성됩니다 — 알려진 평문 누출이 없습니다.
- Poly1305 인증 태그로 암호문의 변조 여부를 확인할 수 있습니다. 단 1바이트라도 수정되거나 잘못된 키가 사용되면 복호화가 즉시 실패합니다.
- 저장 값의 형식은
enc2:<hex(nonce ‖ ciphertext ‖ tag)>(12바이트 논스 + N바이트 암호문 + 16바이트 태그)입니다.
에이전트가 새 값을 자동으로 암호화하므로, 별도의 암호화 명령을 실행할 필요가 없습니다. 자격 증명을 저장하면 설정 파일의 값은 다음과 같이 표시됩니다:
[provider]api_key = "enc2:9f3c1a...e7b204" # ChaCha20-Poly1305 ciphertext, not the real key[secrets] 설정
섹션 제목: “[secrets] 설정”암호화는 기본적으로 활성화되어 있습니다. 평문 설정을 선호하는 소버린 사용자는 이를 비활성화할 수 있습니다.
[secrets]encrypt = true # default — set false to store secrets as plaintext| 키 | 타입 | 기본값 | 의미 |
|---|---|---|---|
secrets.encrypt | boolean | true | true이면 시크릿이 enc2: 암호문으로 봉인되어 기록됩니다. false이면 값이 그대로 저장됩니다. |
secrets.encrypt = false로 설정하면 암호화가 비활성화됩니다. encrypt()는 평문을 그대로 반환하고, 설정 파일에는 원시 값이 저장됩니다. 설정과 관계없이 빈 문자열은 암호화되지 않습니다.
키 파일
섹션 제목: “키 파일”| 속성 | 값 |
|---|---|
| 경로 | ~/.revka/.secret_key |
| 내용 | 32바이트 키, 16진수 인코딩 (64자) |
| POSIX 권한 | 0600, O_CREAT | O_EXCL를 통해 원자적으로 생성되어 읽기 가능한 구간이 존재하지 않음 |
| Windows 권한 | takeown으로 소유권을 설정한 뒤 icacls /inheritance:r /grant:r <user>:F로 현재 사용자에게만 접근 제한 |
enc:에서 enc2:로의 마이그레이션
섹션 제목: “enc:에서 enc2:로의 마이그레이션”이전 Revka 빌드는 enc: 접두사를 사용하는 취약한 XOR 암호를 사용했습니다. 해당 포맷은 보안에 취약하며(알려진 평문 공격에 노출됨), 기존 설정 파일의 호환성을 위해서만 유지되고 있습니다. 현재의 안전한 포맷은 enc2: (ChaCha20-Poly1305)입니다.
마이그레이션은 읽기 시 자동으로 단방향으로 수행됩니다:
enc:값은 레거시 XOR 알고리즘으로 복호화된 뒤 즉시enc2:로 재암호화되고, 업그레이드된 값이 설정 파일에 저장됩니다. 레거시 값이 복호화될 때마다 경고가 기록됩니다.enc2:값은 이미 안전하므로 변경 없이 그대로 유지됩니다.- 접두사가 없는 값은 평문으로 처리되어 변경 없이 반환됩니다.
| 접두사 | 알고리즘 | 상태 |
|---|---|---|
enc2: | ChaCha20-Poly1305 AEAD | 현재, 안전 |
enc: | XOR 반복 키 암호 | 레거시, 취약 — 읽기 시 자동 마이그레이션 |
| (없음) | — | 평문, 그대로 반환 |
내부적으로 사용되며 진단 시 노출되는 감지 헬퍼는 값의 상태를 다음과 같이 분류합니다:
is_encrypted()—enc:또는enc2:중 하나이면 true.is_secure_encrypted()—enc2:인 경우에만 true.needs_migration()— 업그레이드가 필요한enc:값이면 true.
디바이스 페어링 및 베어러 토큰 인증
섹션 제목: “디바이스 페어링 및 베어러 토큰 인증”게이트웨이의 REST 페어링 흐름 외에도, Revka의 보안 레이어에는 게이트웨이와 채널의 디바이스 인증을 담당하는 **PairingGuard**가 포함되어 있습니다. 이 가드는 베어러 토큰을 발급하고, 해시로 저장하며, 페어링 단계를 무차별 대입 공격으로부터 보호합니다.
동작 방식:
require_pairing = true이고 기존 토큰이 없는 최초 시작 시, 6자리 일회용 페어링 코드(CSPRNG)가 터미널에 출력됩니다.- 클라이언트가 코드를 제출하면 256비트(32바이트) 엔트로피를 가진 베어러 토큰을 접두사와 함께 16진수로 인코딩하여 반환합니다. 평문 토큰은 딱 한 번만 표시됩니다.
- 토큰은 SHA-256 해시로만 저장됩니다 — 가드는 평문 토큰을 절대 저장하지 않습니다. 하위 호환성을 위해 평문 토큰과 사전 해시된 (64자 16진수) 토큰 모두 로드 시 허용됩니다.
- 페어링 코드는 타이밍 사이드채널을 방지하기 위해 상수 시간 동등 비교로 검증됩니다.
[gateway]require_pairing = true # default — set false to disable auth (local-only use)allow_public_bind = false # must be explicit to bind beyond localhost| 키 | 타입 | 기본값 | 의미 |
|---|---|---|---|
gateway.require_pairing | boolean | true | 보호된 /api/* 라우트에 베어러 토큰 인증을 요구합니다. 부트스트랩/라이브니스 엔드포인트는 예외입니다. /api/pair는 토큰 없이 페어링 코드를 허용하고, /api/status는 기본 라이브니스 정보를 미인증 상태로 반환합니다(전체 세부 정보는 인증 시에만 제공). false로 설정하면 인증이 완전히 비활성화됩니다. |
gateway.allow_public_bind | boolean | false | 터널 없이 localhost 이상의 인터페이스에 바인딩하려면 명시적으로 설정해야 합니다. |
gateway.paired_tokens | list | (관리됨) | 페어링 성공 시 자동으로 기록되는 해시된 토큰입니다. 직접 수정하지 마세요. |
무차별 대입 잠금
섹션 제목: “무차별 대입 잠금”페어링 가드는 클라이언트별로 코드 제출 실패 횟수를 추적합니다. 너무 많은 실패가 발생하면 해당 클라이언트가 잠금됩니다:
| 상수 | 값 | 의미 |
|---|---|---|
MAX_PAIR_ATTEMPTS | 5 | 잠금 전 허용되는 코드 입력 실패 횟수 |
PAIR_LOCKOUT_SECS | 300 | 잠금 지속 시간 (5분) |
잠금은 클라이언트 단위이며 전역이 아니므로, 한 명의 공격자가 모든 사용자를 잠글 수 없습니다. 임계값 이전에 올바른 코드를 제출하면 정상적으로 페어링됩니다. (게이트웨이는 REST 엔드포인트에 이 위에 IP 기반 AuthRateLimiter 레이어를 추가로 적용합니다 — 페어링 및 인증을 참고하세요.)
최초 사용 흐름
섹션 제목: “최초 사용 흐름”-
게이트웨이를 시작합니다. 터미널에 일회용 페어링 코드가 출력됩니다.
Terminal window revka gateway# Pairing code: 123456 -
코드를 토큰으로 교환합니다 (전체 요청 및 응답 형식은 페어링 API를 참고하세요).
Terminal window curl -X POST http://127.0.0.1:42617/api/pair \-H 'Content-Type: application/json' \-d '{"code": "123456", "device_name": "My Laptop", "device_type": "cli"}' -
반환된 토큰을 저장합니다 — 단 한 번만 표시됩니다. 가드는 SHA-256 해시만 저장합니다.
-
이후의 모든 요청을 토큰으로 인증합니다.
Terminal window curl http://127.0.0.1:42617/api/status \-H "Authorization: Bearer <token>"
코드는 최초 사용 시 소진됩니다 — 코드당 한 번만 페어링하고, 이후에는 새 코드로 교체하세요. 토큰은 성공 시 설정 파일에 저장되므로, 데몬 재시작 후에도 다시 페어링할 필요가 없습니다.
서비스 토큰
섹션 제목: “서비스 토큰”신뢰할 수 있는 로컬 사이드카 — 주로 operator-mcp 런타임 — 는 사용자 베어러 토큰 대신 서비스 토큰으로 머신 간 인증을 수행합니다. 전용 헤더로 전송됩니다:
X-Revka-Service-Token: <service-token>이 헤더는 권한이 필요한 로컬 접근 라우트에서 Authorization: Bearer와 함께 허용됩니다. 서비스 토큰은 게이트웨이 시작 시 자동 생성되며, 사용자 베어러 토큰보다 더 높은 권한을 가진 별도의 자격 증명입니다. 저장된 워크플로 자격 증명을 복호화하고 비용 원장을 기록하는 경로이며, 워크스페이스 에셋 URL 서명에도 동일한 키가 사용됩니다.
민감 값 난독화
섹션 제목: “민감 값 난독화”시크릿이 로그에 유출되지 않도록, Revka는 코드베이스 전반에 걸쳐 모듈 수준의 redact() 헬퍼를 사용합니다. 이 헬퍼는 멀티바이트 UTF-8에서도 패닉 없이 문자 경계를 안전하게 처리하여 앞 4자를 보여주고 나머지를 ***로 대체합니다:
redact("sk-ant-abcdef...") → "sk-a***"redact("abcd") → "***" // 4 or fewer chars → fully masked이는 의도적으로 단순하게 설계된 기능입니다 — 로그 라인이 어떤 자격 증명을 참조하는지 식별할 수 있을 정도로만 노출합니다. zeroize-on-drop 기본 요소가 아니므로 메모리에서 시크릿을 지우지 않습니다. 완전한 보호를 위해서는 암호화 시크릿 저장소 및 아웃바운드 누출 탐지와 함께 사용하세요. redact()는 민감한 값이 로깅되는 지점에서 명시적으로 호출해야 하는 수동 헬퍼입니다. 옵트인한 호출 지점에서만 적용되며 — 자동 로깅 인터셉터가 없으므로, 코드가 redact()를 통해 값을 처리하는 경우에만 난독화됩니다.
강화 체크리스트
섹션 제목: “강화 체크리스트”-
secrets.encrypt = true를 유지하여 모든 자격 증명이enc2:암호문으로 저장되도록 합니다. -
~/.revka/.secret_key를 안전한 위치에 백업합니다 — 이 파일이 없으면 암호화된 시크릿은 복구할 수 없습니다. -
require_pairing = true를 유지하고,require_pairing = false와allow_public_bind = true를 절대 함께 사용하지 마세요. -
레거시 값을 마이그레이션합니다 — 원래 키가 있는 상태로 에이전트를 한 번 로드하여
enc:값이enc2:로 자동 업그레이드되도록 합니다. -
서비스 토큰을 보호합니다 — 신뢰할 수 있는 로컬 사이드카에만 제한하고, 브라우저에 절대 노출하지 마세요.