Skip to content
isdnetworks
Go back

돌아가는 서비스의 컬럼을 암호화하며 정한 순서

(수정됨)

customers.name 과 phone 같은 개인정보 컬럼을 암호화해야 했는데 이미 수백만 행이 평문으로 들어 있었다. 멈추고 한꺼번에 바꾸는 방법을 쓸 수 없어서 단계를 나누는 쪽으로 갔다.

Table of contents

Open Table of contents

배경 — 검색 요건이 정한 범위

가장 오래 걸린 결정은 어디까지 암호화할 것인가였다. 전부 암호화하면 안전하지만 이름으로 찾는 기능이 그대로 죽기 때문이다.

그래서 검색 방식을 기준으로 셋으로 나눴다. 검색이 필요 없는 것은 encrypted 캐스트로 그냥 암호화하고 완전 일치만 하는 것은 phone_hash 컬럼을 함께 두고 부분 검색이 필요한 것은 답을 못 찾은 채로 남겼다.

조치 — 해시 컬럼이 여는 한 갈래

완전 일치 검색은 값을 Crypt::encryptString 으로 저장하면서 그 값의 HMAC-SHA256 을 별도 컬럼에 같이 넣어 두면 된다. Laravel 공식 문서대로 암호화는 AES-256-CBC 이고 값마다 MAC 이 붙으므로 암호문끼리는 같은 평문이라도 달라 검색에 못 쓰고 해시 컬럼이 그 자리를 맡는다. 찾을 때는 입력값을 같은 방식으로 해시해서 그 컬럼과 비교한다.

되는 것은 완전 일치까지이고 앞글자로 찾는 것은 이 방법으로 풀리지 않는다. 부분 검색까지 필요한 컬럼은 미결로 남겨 두고 나머지를 먼저 옮기는 것이 이 단계의 선택이었다.

검증 — 다섯 단계로 나눈 전환

두 가지 방법이 있었는데 점검 창을 잡고 한 번에 바꾸는 것과 컬럼 추가와 양쪽 쓰기와 기존 채우기와 읽기 전환과 평문 삭제 다섯 단계로 나누는 것이다. 뒤쪽을 골랐다. 이유는 수백만 행을 암호화하는 시간만큼 서비스를 세울 수 없어서다. 대신 평문과 암호문이 몇 주 공존하는 기간을 감수했다. 앞의 두 단계는 배포해도 겉으로 아무것도 바뀌지 않으므로 되돌리기가 쉽다.

여기서 놓치기 쉬운 것은 배치가 도는 동안에도 서비스가 계속 돌아간다는 점이었다. 배치가 읽은 뒤에 그 행이 수정되면 옛 값이 암호화되므로 updated_at 을 보고 배치 시작 이후에 바뀐 것을 다시 처리했다.

주의 — 키 보관과 버전 표시

암호화 자체보다 키를 어디에 두느냐가 더 어려운 문제였다. 코드에 넣으면 저장소를 보는 사람이 전부 알게 되고 설정 파일이면 서버에 들어오는 사람이 알게 된다.

Secrets Manager 가 가장 낫지만 그 보관소에 접근하는 방법이 또 필요하므로 인프라 작업을 남기고 .env 의 APP_KEY 에 두되 권한을 제한했다. 키를 바꾸면 기존 데이터를 전부 다시 암호화해야 하므로 암호문 앞에 v1: 키 버전을 붙여 여러 키가 공존할 수 있게 했다.

원인 — 미리 챙기지 못한 네 가지

평문보다 길어진다는 것을 첫 단계에서 안 챙겨서 VARCHAR(100) 그대로 채우는 단계에서 값이 잘렸다. 암호화된 값으로는 정렬이 의미를 잃으므로 이름순 정렬 기능도 같이 못 쓰게 됐다.

데이터베이스를 암호화해도 로그에 평문이 그대로 찍히고 있어서 그쪽은 따로 찾아 고쳤다. 예전 백업 파일에 남은 평문은 처리 방법을 정하지 못한 채로 남겨 뒀다.

정리


Share this post on:

Previous Post
한 번도 채워진 적 없는 컬럼
Next Post
정규식 파싱이 놓치는 설정 형식