headless
전체 블로그

// 기술 블로그

봉투 암호화로 자격증명 보관 — headless의 키 관리 모델

은행·홈택스 자격증명을 어떻게 보관하는지 적습니다. 데이터키를 마스터키로 한 번 더 감싸고, 평문은 수집 시점 메모리에서만 잠깐 존재합니다.

·headless

금융 데이터 수집기는 고객의 은행·홈택스 자격증명을 들고 있어야 동작합니다. 그 자격증명을 어떻게 보관하느냐가 이 제품의 신뢰를 좌우합니다. headless가 쓰는 봉투 암호화(envelope encryption) 모델과, 그 모델로도 못 없애는 한 가지를 정직하게 적습니다.

보안은 기능이 아니라 기본값입니다. 과장 없이 설계와 한계를 같이 둡니다.

풀려고 한 문제

요구는 단순합니다. 자격증명을 저장하되, 저장소가 통째로 유출돼도 평문이 바로 드러나지 않아야 합니다.

  • 자격증명은 공동인증서(PFX), 아이디·비밀번호, 기관 토큰이 섞입니다.
  • 수집은 비동기입니다 — 등록 시점과 사용 시점이 분리됩니다. 그 사이 어딘가에 자격증명이 머뭅니다.
  • 한 워크스페이스의 자격증명이 다른 워크스페이스에서 보이면 안 됩니다.

이 마지막 조건은 headless를 어떤 형태로 쓰든 따라옵니다. 외부 개발자가 자기 서비스에 데이터를 통합하든, 회사가 자기 내부 정산 시스템에 적재하든, 1인 사업자가 로컬에서 받아 보든, 자격증명은 결국 워크스페이스 경계 안에 갇혀야 합니다. 종착지가 어디든 격리 단위는 하나입니다.

가장 단순한 해법은 "DB 컬럼을 한 키로 암호화"입니다. 이건 키 하나가 새면 전부 새는 구조입니다. 단일 키가 코드·환경·백업 어딘가에 같이 사는 한, 저장소 유출이 곧 평문 유출이 됩니다. 이 사슬을 끊는 게 목표였습니다.

우리가 고려한 옵션들

옵션장점단점안 고른 이유
A. 단일 키 컬럼 암호화단순키 1개 유출 = 전수 유출사고 반경이 너무 큼
B. 자격증명마다 독립 키반경 최소키 자체의 보관 문제가 그대로 남음키 관리 문제를 미루기만 함
C. 봉투 암호화 (선택)데이터키 분리 + 마스터키로 보호, 반경 격리키 계층·회전 복잡도

C를 골랐습니다. 복잡도를 받는 대신, 저장소 유출이 곧 평문 유출이 되지 않게 했습니다.

선택한 설계

핵심은 키를 두 겹으로 두는 것입니다.

자격증명 평문
   │  (1) 데이터키(DEK)로 AES-256 암호화

암호문 ──저장──> DB

데이터키(DEK)
   │  (2) 마스터키(KEK)로 다시 암호화

암호화된 DEK ──저장──> DB (암호문 옆)

마스터키(KEK)  — 별도 키 관리 영역에서만 산다 (DB 밖)
  • 자격증명은 데이터키로 암호화해 DB에 둡니다.
  • 그 데이터키 자체도 평문으로 두지 않습니다. 마스터키로 한 번 더 감싼 형태로만 저장합니다.
  • 마스터키는 DB가 아니라 분리된 키 관리 영역에 있습니다. DB 백업만 유출돼서는 데이터키를 풀 수 없습니다.

복호화는 수집을 실행하는 그 순간 메모리에서만 일어납니다. 등록 시점에 평문은 암호화되어 사라지고, 다음에 평문이 존재하는 건 스크래핑이 도는 짧은 동안의 메모리뿐입니다. 그 메모리도 작업이 끝나면 버립니다. 워크스페이스마다 키 경계가 갈려서, 한 워크스페이스의 데이터키로 다른 워크스페이스 자격증명을 풀 수 없습니다.

핵심 흐름 발췌

등록과 사용의 두 지점만 보면 모델이 보입니다.

등록:  plaintext --(DEK, AES-256)--> ciphertext
       DEK       --(KEK)-----------> wrappedDEK
       store(ciphertext, wrappedDEK)         # 평문 폐기

수집:  load(ciphertext, wrappedDEK)
       DEK   = unwrap(wrappedDEK, KEK)        # 메모리
       plain = decrypt(ciphertext, DEK)       # 메모리, 사용 즉시 폐기

비밀번호 같은 계정 인증값은 또 다릅니다. 고객 로그인 비밀번호는 복호화할 일이 없으므로 BCrypt 단방향 해시로만 둡니다. 되돌려야 하는 것(자격증명)과 그럴 필요가 없는 것(비밀번호)을 다른 방식으로 다룹니다.

실제로 무엇이 일어나나

  • 저장소(또는 그 백업) 단독 유출로는 평문이 나오지 않습니다. 마스터키가 같이 새야 합니다 — 두 곳을 동시에 뚫어야 하는 구조입니다.
  • 사고 반경이 워크스페이스 단위로 잘립니다. 키 경계가 곧 격리 경계입니다. 한 워크스페이스를 외부 통합에 쓰든, 내부 적재 파이프라인에 쓰든, 개인 대시보드에 쓰든, 격리 폭은 똑같이 그 워크스페이스 하나로 닫힙니다.
  • 평문 노출 시간은 "네트워크 왕복 + 메모리 처리" 동안으로 수렴합니다. 0은 아닙니다 — 이 점은 숨기지 않습니다.

우리가 틀렸던 것

  • 평문은 0이 아닙니다. 외부 기관에 로그인해 데이터를 받아오려면, 그 순간 메모리에는 평문이 존재할 수밖에 없습니다. 봉투 암호화는 "저장된 평문"을 없애지, "사용 중 평문"을 없애지 못합니다. 우리가 줄인 건 노출 시간이지 노출 가능성 자체가 아닙니다.
  • 키 회전은 공짜가 아닙니다. 마스터키를 교체하면 감싼 데이터키들을 다시 감싸야 합니다. 이 절차를 운영에 자동화로 녹이지 않으면 회전이 미뤄지고, 미뤄진 회전은 그 자체로 위험합니다.
  • 감사 로그의 완전성. "언제 어떤 자격증명이 복호화됐는가"를 남기지만, 로그 자체가 신뢰할 수 있어야 의미가 있습니다. 로그 무결성은 별도 과제로 계속 다듬는 중입니다.

다시 한다면 키 회전 절차를 설계 첫날에 운영 자동화로 같이 그렸을 것입니다. 모델보다 운영이 약한 고리였습니다.

그래서

비슷한 문제(되돌려야 하는 비밀을 다수 보관)를 푼다면 먼저 이걸 봅니다.

  • 단일 키로 시작하지 않습니다. 사고 반경을 처음부터 격리 단위(여기선 워크스페이스)에 맞춥니다.
  • "사용 중 평문"은 못 없앤다는 걸 인정하고, 노출 시간을 줄이는 쪽으로 설계합니다.
  • 키 회전·감사 로그를 기능이 아니라 운영 절차로 같이 만듭니다.

자격증명 격리, 데이터 흐름, 권한 모델을 한자리에서 보려면 보안 개요부터 읽어 보시면 좋습니다. 검토용 상세 자료는 보안 백서에 정리돼 있습니다.

더 읽기