KIMS

데스크톱 앱 API 키 저장, 평문 파일에서 OS 자격증명 저장소로

기술 · 2026-09-22 · KIMS 팀

데스크톱 앱에서 API 키를 평문 파일로 저장하면 다른 프로세스가 그대로 읽을 수 있습니다. Keychain·Credential Manager로 옮긴 과정을 정리했습니다.

데스크톱 앱에 API 키 저장 기능을 처음 붙일 때는 사용자 홈 디렉터리에 config.json 하나를 두고 그 안에 키 문자열을 그대로 적어 넣었습니다. 앱을 켤 때 그 파일을 읽어 헤더에 넣으면 끝이었습니다. 문제는 이 파일을 앱만 읽는 게 아니라는 걸 나중에야 알았다는 점입니다. 같은 계정으로 실행되는 다른 프로그램이라면 경로만 알면 누구나 열어볼 수 있는 파일이었습니다.

문제

평문 파일 저장의 위험은 파일 시스템 권한 구조에서 나옵니다. 같은 사용자 계정 아래에서 돌아가는 프로세스는 대부분 서로의 홈 디렉터리 파일에 접근할 수 있습니다. 즉 악성 확장 프로그램이나 잘못 설치된 유틸리티 하나가 config.json 경로만 알면 API 키를 그대로 읽어갈 수 있다는 뜻입니다. 앱 자체의 코드가 안전해도, 키를 어디에 어떻게 보관하는지가 뚫리면 그 위에 쌓은 인증·요청 로직은 의미가 없어집니다.

더 골치 아픈 건 사용자가 이 위험을 체감하기 어렵다는 점입니다. 앱은 정상적으로 동작하고, 키가 새어나갔다는 신호는 어디에도 없습니다. 문제가 드러나는 시점은 대개 이상한 API 사용량 청구서를 받은 뒤입니다.

시도한 방법

처음 택한 대안은 파일을 암호화하는 것이었습니다. 고정 키를 바이너리에 심어두고 그 키로 config.json 내용을 암호화해 저장하면, 파일을 열어봐도 문자열이 그대로 보이지 않으니 안전할 거라 생각했습니다. 실제로 며칠은 그렇게 운영했습니다.

하지만 이 방식은 근본적으로 같은 문제를 이름만 바꾼 것이었습니다. 암호화에 쓴 고정 키가 바이너리 안에 있으니, 바이너리를 디컴파일하거나 실행 중 메모리를 들여다보면 그 키도 결국 꺼낼 수 있습니다. 자물쇠와 열쇠를 같은 서랍에 넣어둔 셈입니다. 이 방식으로는 "평문보다 조금 어려운 평문" 이상을 만들지 못한다는 걸 인정하고 접근을 바꿨습니다.

해결

결국 답은 애플리케이션이 직접 암호화 로직을 구현하는 게 아니라, OS가 이미 제공하는 자격증명 저장소를 쓰는 것이었습니다. macOS는 Keychain, Windows는 Credential Manager를 쓰는데, 이 둘은 이름도 접근 방식도 다릅니다. 그래서 앱 코드에서는 플랫폼을 감추는 얇은 래퍼 하나를 두고, 실제 저장은 OS에 위임하는 구조로 정리했습니다.

// 의사코드 — 실제 라이브러리 API는 사용 환경에 따라 다름
function saveApiKey(provider, key) {
  if (platform === 'darwin') {
    keychain.setPassword('kims-app', provider, key)
  } else if (platform === 'win32') {
    credentialManager.write(`kims-app:${provider}`, key)
  }
}

이 구조로 바꾸면서 걸린 건 두 저장소의 접근 모델 차이였습니다. Keychain은 앱 서명(코드 사이닝)과 묶여서 접근 권한을 검사하는 반면, Credential Manager는 같은 사용자 세션 안에서 비교적 느슨하게 접근을 허용합니다. 같은 코드를 그대로 두 플랫폼에 돌리면 macOS에서는 매번 시스템 암호 입력 다이얼로그가 뜨는 문제가 생겼고, 이를 앱 서명 설정을 정리해서 해결해야 했습니다. 결과적으로 저장소 자체는 OS가 관리하니 파일 하나를 훔쳐서 키를 빼가는 경로는 막히지만, 그 저장소에 정상적으로 접근하는 절차는 플랫폼마다 따로 맞춰야 했습니다.

결과

항목이전 (평문 파일)이후 (OS 자격증명 저장소)
저장 위치사용자 홈 디렉터리 config.jsonmacOS Keychain / Windows Credential Manager
다른 프로세스 접근같은 계정이면 파일을 직접 열람 가능OS 인증 절차를 거쳐야 접근 가능
키 보관 형태평문 또는 앱 내장 고정 키로 암호화OS가 관리하는 암호화 저장소

표로 정리하면 바뀐 건 "저장 장소를 OS에 넘겼다"는 한 가지지만, 그 한 가지가 앱이 자체적으로 감당해야 했던 암호화·권한 관리 책임을 OS 쪽으로 옮긴 것이기도 합니다.

메모리에 남는 키도 위험합니다저장소에서 안전하게 꺼내온 키라도, 앱이 그걸 변수에 오래 들고 있으면 문제가 됩니다. 프로세스 메모리를 덤프하면 그 시점에 메모리에 있던 문자열이 그대로 노출될 수 있습니다. 그래서 키는 API 호출 직전에 저장소에서 꺼내 쓰고, 쓴 뒤에는 참조를 남겨두지 않는 방향으로 다뤄야 합니다.

남은 문제

OS 저장소로 옮겼다고 끝난 건 아닙니다. 먼저 앱을 삭제해도 Keychain이나 Credential Manager에 등록된 항목은 대부분 그대로 남습니다. 앱 설치 폴더를 지우는 것과 OS 자격증명 저장소를 정리하는 것은 별개의 절차이기 때문에, 제거 과정에 그 항목을 함께 지우는 절차를 넣지 않으면 사용자 시스템에 죽은 키가 계속 남아 있게 됩니다.

또 하나는 사용자가 스스로 키를 폐기하고 새 키로 바꿀 경로가 앱 안에 있어야 한다는 점입니다. 저장 방식이 아무리 안전해도, 키가 유출됐다고 의심될 때 사용자가 앱 밖으로 나가서 설정 파일을 찾아 지우는 수밖에 없다면 그 자체가 또 다른 위험 요소입니다. 앱 안에 "이 키 폐기하고 새로 등록" 버튼 하나를 두는 게 저장 구조 자체보다 실무적으로는 더 중요할 때가 많습니다.

메모리 노출 문제도 완전히 해소된 건 아닙니다. API 호출 시점마다 저장소를 조회하는 구조로 바꿔도, 호출 순간에는 결국 키가 메모리에 존재합니다. 이 시점의 위험을 얼마나 줄일 수 있는지는 OS 수준의 메모리 보호 기능에 의존하는 부분이 있어, 앱 코드만으로 전부 막을 수 있다고 단정하기는 어렵습니다.

정리

평문 파일에서 OS 자격증명 저장소로 옮기는 작업은 코드 몇 줄을 바꾸는 문제가 아니라, macOS와 Windows가 자격증명을 다루는 방식 자체가 다르다는 걸 받아들이고 그 차이를 흡수하는 레이어를 만드는 작업이었습니다. 저장 위치를 옮긴 뒤에도 삭제 시 정리, 폐기·교체 경로, 메모리 체류 시간은 별도로 챙겨야 하는 항목으로 남습니다.

이 구조는 이렇게 쓰고 있습니다

이 글에서 다룬 저장 구조는 Windows·macOS 데스크톱 앱에서 본인 API 키 연결 기능을 뒷받침합니다. Claude·GPT·Gemini 중 쓰고 싶은 모델의 키를 직접 등록해두면, 브라우저를 열지 않고도 앱을 상시 실행 상태로 두고 바로 쓸 수 있습니다. 키는 각 OS의 자격증명 저장소에 보관되며, 앱 안에서 언제든 다시 등록하거나 바꿀 수 있는 경로를 열어두고 있습니다.

API 키 관리데스크톱 앱KeychainCredential Manager보안
KIMS AI 도구를 직접 붙여 쓰는 개인 사용자·개발자를 위한 서비스 — 자세히 보기