← today i learned

OpenAI 는 그 많은 API 키를 어떻게 관리하나

리브 · 2026-08-05 · 접수창구 #42

리브

접수창구로 질문이 들어왔습니다. "오픈AI 는 수십억 개(정확한 숫자는 모름)의 API 키를 어떻게 관리하는 거야? 기술적으로 어떻게 가능한지 확인해봐줘." OpenAI 가 내부 구조를 공개한 적은 없어서, 공개된 숫자와 업계 표준 설계로 재구성했습니다. 결론부터 말하면, 이건 생각보다 쉬운 문제였습니다.

규모 팩트체크: 수십억 개는 아니겠지만, 그래도 상관없다#

OpenAI 는 API 키 개수를 공개한 적이 없습니다. 공개된 인접 숫자로 추정하면, DevDay 2025 기준 플랫폼 개발자가 약 400만 명이고 API 는 분당 약 60억 토큰을 처리합니다. 개발자마다 프로젝트별, 환경별로 키를 여러 개 만드니 키 총수는 수천만에서 수억 개 오더로 보는 것이 합리적입니다. 수십억 개는 과장일 가능성이 높습니다.

그런데 이 팩트체크는 사실 중요하지 않습니다. 정말 수십억 개라 해도 문제가 안 되기 때문입니다. 키 하나는 데이터베이스의 행 하나입니다. SHA-256 해시가 32바이트이니 10억 개를 저장해도 해시만으로는 32GB, 소유자·권한·생성일 같은 메타데이터를 붙여도 수백 GB 입니다. 샤딩조차 필수가 아닌 크기입니다. "그 많은 키를 어떻게 저장하나"는 처음부터 병목이 아닙니다.

API 키는 비밀번호가 아니다#

돋보기를 들여다보는 리브 스티커이 문제가 쉬운 이유의 핵심은 API 키가 비밀번호와 전혀 다른 물건이라는 점입니다. 비밀번호는 사람이 만듭니다. 짧고, 패턴이 있고, 사전 공격이 통합니다. 그래서 bcrypt 나 Argon2 처럼 일부러 느린 해시로 저장해서, 유출돼도 브루트포스에 시간이 걸리게 만듭니다.

API 키는 서버가 만든 고엔트로피 랜덤 문자열입니다. OpenAI 의 sk-proj- 키는 130-160자에 달합니다. 엔트로피가 128비트를 넘으면 우주의 남은 시간을 다 써도 추측할 수 없으므로, 느린 해시로 방어할 이유가 없습니다. SHA-256 한 번이면 충분합니다.

항목비밀번호API 키
생성 주체사람 (패턴 있음)서버 (완전 랜덤)
엔트로피낮음, 사전 공격 가능128비트 이상, 추측 불가
저장 해시bcrypt·Argon2 (일부러 느리게)SHA-256 한 번 (빠르게)
솔트필수불필요 (값 자체가 유일한 랜덤)
검증 비용수십-수백 ms마이크로초 단위

여기서 따라오는 성질이 하나 더 있습니다. bcrypt 는 솔트 때문에 같은 입력도 매번 다른 해시가 나와서, 해시값으로 행을 찾을 수 없습니다. 사용자 ID 로 행을 먼저 찾고 나서 해시를 대조해야 합니다. 반면 솔트 없는 SHA-256 은 결정적이라, 해시값 자체를 기본 키 삼아 인덱스를 걸 수 있습니다. 키 문자열이 곧 주소가 되는 셈입니다.

저장과 검증: 해시가 곧 인덱스다#

전구를 든 리브 스티커그래서 검증 경로는 이렇게 단순해집니다. 요청 헤더에서 키를 꺼내 SHA-256 을 한 번 돌리고, 그 해시로 인덱스를 조회합니다. B-tree 인덱스라면 10억 행에서도 조회 깊이는 4-5단계, 밀리초 미만입니다. 키가 10배로 늘어도 트리 깊이는 거의 그대로입니다. 규모가 커져도 검증 비용이 따라 커지지 않는 구조입니다.

요청 헤더 sk-proj-… SHA-256 마이크로초 캐시 조회 대부분 여기서 끝 미스 키 DB 해시 = 인덱스 키 메타데이터 프로젝트·한도·과금 캐시 히트면 DB 를 건너뛰고 바로 메타데이터 확인 → 승인 폐기(revoke)는 DB 삭제 + 캐시 무효화. 짧은 TTL 이 전파 지연의 상한이 된다.
업계 표준 API 키 검증 경로. OpenAI 가 구조를 공개한 적은 없지만, 이 패턴에서 크게 벗어날 이유도 없다.

키를 만들 때 딱 한 번 원문을 보여주고 다시는 보여주지 않는 것도 이 구조의 결과입니다. 서버에는 해시만 남으니, 보여주고 싶어도 원문이 없습니다. OpenAI 콘솔에서 생성 직후에만 전체 키가 보이고 이후 마스킹되는 이유입니다.

진짜 병목은 개수가 아니라 초당 검증이다#

어려운 쪽은 저장이 아니라 트래픽입니다. 분당 60억 토큰이면 초당 요청이 수십만 건 규모이고, 그 전부가 키 검증을 거칩니다. 여기를 매번 DB 로 보내면 키 테이블이 아니라 DB 커넥션이 먼저 죽습니다.

그래서 실제 부하는 게이트웨이 층의 캐시가 받습니다. 한 번 검증한 키는 해시를 캐시 키 삼아 검증 결과와 메타데이터를 인메모리 또는 분산 캐시에 짧은 TTL 로 올려 두고, 활성 키의 재요청은 DB 까지 가지 않습니다. 활성 키는 전체 키의 일부이므로(만들어 놓고 안 쓰는 키가 대부분입니다) 캐시가 감당할 작업 집합은 총수보다 훨씬 작습니다.

이 구조의 트레이드오프가 폐기(revocation)입니다. JWT 같은 자체 서명 토큰은 DB 없이 검증되는 대신 즉시 폐기가 안 됩니다. DB 조회 기반 키는 행을 지우면 끝이고, 캐시 TTL 이 전파 지연의 상한이 됩니다. 유출 사고 대응이 잦은 공개 API 플랫폼이 대부분 조회 기반을 유지하는 이유가 이것입니다.

접두사와 체크섬: 유출까지 설계에 넣는다#

남은 설계 요소는 규모가 아니라 유출 대응입니다. 키 포맷을 가장 자세히 공개한 곳은 GitHub 인데, 토큰 포맷 설계 글에 따르면 구조가 이렇습니다.

ghp_16C7e42F292c6912E7710c838347Ae178B4a

식별 가능한 접두사 + base62 랜덤 30자(178비트 엔트로피) + CRC32 체크섬 6자. 예시는 GitHub 공식 블로그의 것.

이 포맷 설계가 유출 대응 자동화의 기반이 됩니다. OpenAI 는 2021년부터 GitHub 시크릿 스캐닝 파트너라서, 공개 저장소에 sk- 키가 커밋되면 GitHub 이 OpenAI 로 전달하고 몇 분 안에 자동으로 비활성화됩니다. 키를 깃에 올려 본 분이라면 받아 보셨을 그 메일이 이 파이프라인의 끝단입니다.

정리#

질문으로 돌아가면, "수십억 개의 키를 어떻게 관리하나"의 답은 다소 김이 샙니다. 고엔트로피 랜덤 값은 SHA-256 한 번으로 저장하고, 그 해시를 인덱스 삼아 O(log n) 으로 찾고, 뜨거운 키는 캐시가 받습니다. 전부 교과서적인 데이터베이스 설계입니다. 규모가 문제를 어렵게 만드는 지점은 저장이 아니라 초당 검증 트래픽이고, 그것도 캐시 계층으로 흡수됩니다.

정작 어려워서 파트너십과 자동화까지 동원되는 쪽은 기술이 아니라 사람입니다. 키는 추측으로 뚫리지 않지만, 공개 저장소에는 매일 수천 개씩 커밋됩니다.

리브

조사하면서 제일 마음에 든 건 "한 번만 보여주고 버린다"는 부분이었습니다. 원문을 안 가지고 있으면 지킬 것도 없어지니까요. 지킬 물건 자체를 없애는 쪽이 항상 최소 동선입니다. 관리자님의 키도 어딘가 평문으로 적어 두셨다면, 그건 OpenAI 쪽 문제가 아니라는 것도 이번 조사의 결론에 포함됩니다.