비밀번호가 바뀌면 암호화된 데이터는 어떻게 될까? 전체를 다시 처리하지 않는 '키 포장(Key Wrapping)' 메커니즘
요약
개인 정보를 암호화할 때 비밀번호 변경 시 전체 데이터를 재암호화하는 것은 위험합니다. 이 글은 '데이터 키 포장(Envelope Encryption)' 방식을 제안하며, 무작위 데이터 키를 관리자 비밀번호로만 암호화(포장)하는 2단계 구조가 핵심입니다. 이를 통해 비밀번호 변경 시 원본 이름 암호문은 그대로 두고 포장된 키 정보만 안전하게 업데이트할 수 있습니다.
핵심 포인트
- 비밀번호 직접 암호화는 비밀번호와 암호문이 1대1로 연결되어 재작성 위험이 높습니다.
- 해결책은 데이터 키(Data Key)를 만들고, 이를 포장 키(Wrapping Key)로만 암호화하는 2단계 구조입니다.
- 비밀번호 변경 시에는 이름 암호문 대신 '포장이 담긴 데이터 키' 한 줄만 업데이트합니다.
- Web Crypto API 등 브라우저 표준 기능만을 사용하여 구현이 가능합니다.
개인 정보를 암호화하여 저장하는 시스템을 만들 때, 가장 먼저 떠올리는 방식은 '관리자 비밀번호로 키를 생성한 다음, 그 키로 이름을 암호화'하는 형태입니다.
작동 자체는 가능합니다. 하지만 실제 운영에 들어가면 다음과 같은 상황에서 어려움을 겪게 됩니다.
- 관리자가 교체되어 비밀번호를 변경해야 할 때
- 관리자를 한 명 더 추가하고 싶지만, 동일한 비밀번호를 공유하고 싶지 않을 때
- 동일한 데이터를 다른 컴퓨터에서도 읽어야 할 때
본 글에서는 이러한 변경 사항을 데이터에 전혀 손대지 않고 처리할 수 있는 '데이터 키 포장(Data Key Wrapping)' 설계(엔벨로프 암호화라고도 불립니다)를, 제가 사내용으로 만들고 있는 업무 시스템(스프레드시트 + Google Apps Script와 Windows 상주 도구)을 예시로 정리합니다.
이는 이전 글인 '환자 번호나 직원 번호를 '대조는 할 수 있지만, 유출되어도 읽을 수는 없는' 형태로 보유하기'에서 마지막에 '다른 글로 작성하겠다'고 했던 부분입니다.
비밀번호로 직접 암호화할 경우의 문제점
비밀번호로부터 만든 키로 이름을 직접 암호화하면, 비밀번호와 암호문이 1대1로 연결됩니다.
| 하고 싶은 일 | 비밀번호로 직접 암호화했을 경우 |
|---|---|
| 비밀번호 변경 | 전원분의 데이터를 이전 키로 열고, 새 키로 다시 암호화하여 시트에 재작성해야 함 |
| ... | |
| 여기서 오해하기 쉬운 부분이 있는데, 문제는 계산 속도가 아닙니다. 직접 테스트해보면 1,000건을 다시 암호화하는 데 걸린 시간은 22ms(Apple M1・Node.js)에 불과했습니다. |
문제가 되는 것은 전체 데이터를 재작성하는 행위 자체입니다. 작업 도중에 중단되면, 이전 키의 행과 새 키의 행이 뒤섞이게 됩니다. 재작성하는 사이에 누군가 신청을 하면, 해당 행이 어느 키로 암호화된 것인지 알 수 없게 됩니다. 비밀번호를 바꿀 때마다 이러한 위험한 작업이 발생합니다.
데이터 키를 하나 만들고, 그것을 '포장'하기
해결책은 키를 2단계로 만드는 것입니다.
- 데이터 키(Data Key): 무작위로 만든 키. 이름 암호화에는 이것만 사용한다. 한 번 만들면 변경하지 않는다 -
- 포장 키(Wrapping Key): 관리자 비밀번호로부터 만든 키. 데이터 키를 암호화(포장)하는 용도로만 사용한다
저장되는 것은 '포장된 데이터 키'(수십 바이트)뿐입니다. 비밀번호도, 데이터 키도 원본 형태로 어디에도 저장하지 않습니다.
이렇게 하면 앞서의 표가 다음과 같이 변경됩니다.
| 하고 싶은 일 | 데이터 키를 포장하는 경우 |
|---|---|
| 비밀번호 변경 | 이전 비밀번호로 포장을 열고, 새 비밀번호로 다시 포장한다. 이름 암호문에는 손대지 않는다 |
| 관리자 추가 | 동일한 데이터 키를 해당 사람의 비밀번호로 한 번 더 포장한다 |
| 관리자 퇴사 | 해당 사람의 포장을 삭제한다(※후술할 한계 있음) |
| 다른 단말에서 읽기 | 해당 단말 전용 보호로 한 번 더 포장한다 |
재작성되는 것은 오직 포장이 담긴 1줄뿐입니다.
코드: 브라우저 표준 기능만으로 작성하기
암호화 구성 요소는 직접 만들지 않습니다. 브라우저(와 Node.js)에 기본적으로 내장된 Web Crypto API 만으로 작성할 수 있습니다. 아래는 설명용으로 작성한 최소 예시입니다.
비밀번호로부터 '포장 키'와 '확인 값' 만들기
const { subtle } = globalThis.crypto;
const enc = new TextEncoder();
async function fromPassword(password, salt) {
...
단 한 번의 계산으로 역할이 다른 두 가지 값을 분리해냅니다.
- 포장 키: 브라우저 내부에서만 사용하며 서버로 전송하지 않습니다 -
- 확인 값: 서버로 전송되어 '비밀번호가 올바른지' 판별하는 데 사용됩니다. 서버가 가진 것은 이 해시값뿐입니다
서버는 '비밀번호가 맞는지'는 알지만, '포장을 열 키'는 가지고 있지 않습니다. 따라서 서버 계정이 탈취되더라도 그것만으로는 이름을 읽을 수 없습니다.
초기 설정: 데이터 키를 만들고 포장하기
async function setup(password) {
const salt = crypto.getRandomValues(new Uint8Array(16));
const dataKey = await subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']);
...
매번: 포장 열기 (추출 불가능한 형태로)
평소에는 extractable = false로 열 수 있습니다. 이렇게 하면, 열린 데이터 키는 브라우저 내에서 사용할 수는 있지만, 그 내용물 자체를 추출할 수는 없는 상태가 됩니다. 화면 속 스크립트에 오류가 발생하더라도, 데이터 키 자체를 외부로 기록하는 것은 불가능합니다.
비밀번호 변경: 열고, 재포장만 하면 끝
async function changePassword(oldPw, newPw, salt, wrapped) {
const dataKey = await open(oldPw, salt, wrapped, true); // 이 경우에 한해 추출 가능한 형태로 엽니다
const newSalt = crypto.getRandomValues(new Uint8Array(16));
...
직접 실행한 결과입니다.
재포장(ms) 289 wrapped bytes 40
새 비밀번호로 복호화: 야마다 타로
기존 비밀번호: OperationError
...
- 재포장은 약 0.3초가 소요됩니다. 이 시간의 대부분은 의도적으로 지연시킨 부분(60만 회) 때문입니다.
- 새 비밀번호로는, 변경 전에 암호화된 이름이 그대로 읽힙니다. - 기존 비밀번호로는 열 수 없습니다 (AES-KW는 포장이 손상되지 않았는지까지 확인하므로, 잘못된 키를 사용하면 오류가 발생합니다).
- 평소의 형태로 열린 데이터 키는 추출하려고 해도 거부됩니다.
여러 방식으로 포장할 수 있다: 단말기 보호와 암호문(비밀번호)
같은 원리를 Windows 상주 도구에서도 사용하고 있습니다. 이 시스템은 병원 내 공유 폴더에 있는 대상자 데이터를 정해진 몇 대의 PC에서 매일 읽어들이는 방식입니다.
매일 아침 비밀번호를 입력받는 것은 현실적이지 않습니다. 그래서, 하나의 데이터 키를 두 가지 방식으로 포장하고 있습니다.
| 포장 방식 | 사용 상황 |
|---|---|
| 단말기별 OS 표준 보호 (해당 단말기에서만 열 수 있음) | 매일의 동작. 아무것도 입력하지 않음 |
| 관리자 암호문(비밀번호) | 단말기를 추가하거나 교체할 때만 |
새로운 단말기를 추가할 때는, 관리자 비밀번호로 포장을 풀고, 그 단말기의 보호 방식으로 다시 한번 포장하여 저장합니다. 다른 단말기나 데이터에는 접근할 수 없습니다.
여기서도 부품을 직접 만든 것은 아닙니다. 구형 실행 환경에는 AES-GCM과 같은 '암호화와 위변조 감지 기능이 통합된 방식'이 없었기 때문에, 표준 암호화 방식과 표준 위변조 감지 방식을 결합하여 사용하고 있습니다.
서버의 '확인'은 공통 진입점에서 계산한다
비밀번호 확인에는 실패 횟수 제한(몇 번 틀리면 일정 시간 동안 잠금)을 걸었습니다. 여기서 한 번, 리뷰를 통해 지적받았습니다.
처음에는, 횟수 제한을 로그인 화면의 처리 과정에만 두고 있었습니다. 그런데 Google Apps Script의 웹 앱에서는, 화면에서 호출할 수 있는 함수가 화면을 거치지 않고 직접 호출될 수도 있습니다. 로그인 화면을 건너뛰고 다른 관리용 함수로 잘못된 비밀번호를 몇 번이고 보낼 수 있게 됩니다.
수정 방법은, 횟수 제한을 모든 관리용 처리가 반드시 지나가는 공통 인증 함수로 옮기는 것이었습니다.
횟수 제한은 '진입 화면'이 아니라, '지켜야 할 처리 과정이 반드시 거치는 부품(함수)'에 두는 것입니다.
이 지적을 해준 것은 설계와 리뷰를 담당하는 AI였습니다. 구현을 맡은 AI와 분리되어 있기 때문에, 작성자 본인도 발견하기 어려운 허점(우회 경로)이 발견되었습니다.
이 설계로 보호할 수 없는 것들
- 퇴사한 관리자의 포장을 지워도, 그 사람이 데이터 키를 기억하고 있다면 읽을 수 있습니다. 포장을 지우는 것은 '앞으로 그 사람의 비밀번호로는 열지 못하게 하는 것'이지, '이미 알고 있는 키를 잊게 만드는 것'이 아닙니다. 정말로 배제하려면, 데이터 키 자체를 새로 만들고 전체 데이터를 재암호화해야 합니다 (이때만은, 다시 쓰기(재작업)를 피할 수 없습니다).
- 포장이 하나밖에 없으면, 비밀번호를 잊어버리는 순간 모두의 데이터가 읽을 수 없게 됩니다. 관리자용 포장은 두 개 이상 보유하는 것이 안전합니다 (예: 예비 암호문을 종이에 적어 봉인하여 보관). - 화면에 표시된 이름은 그 화면을 보고 있는 사람에게는 읽힐 수 있습니다. 암호화는 저장 장소의 보호입니다.
요약
- 비밀번호로 데이터를 직접 암호화하면, 비밀번호 변경이나 관리자 교체 때마다 전체 데이터 재작성이 발생한다. 문제는 속도가 아니라 재작성의 위험성이다. 임의의 **데이터 키(Data Key)**를 하나 만들고, 비밀번호에서 만든 키로 포장하여 저장한다. 변경은 포장만 다시 하면 된다. 포장은 여러 종류를 가질 수 있다. 관리자별, 단말기별로 포장하면 매일의 번거로움 없이 추가나 교체에 대응할 수 있다.
- 비밀번호로부터 '포장 키(Wrapping Key)'와 '확인용 값'을 분리하여 만들고, 서버에는 포장 키를 전달하지 않는다.
- 호출 횟수 제한(Rate Limiting)은 지키고자 하는 처리가 반드시 통과하는 공통 부품에 설정한다.
논의 (Discussion)

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