Skip to content
isdnetworks
Go back

암호화 방식을 바꾸면서 옛 값을 어떻게 할지 정했다

MySQL에 저장돼 있는 개인정보의 암호화 방식을 바꾸기로 결정했다. 새 방식으로 쓰는 코드를 만드는 것보다 이미 저장된 값을 어떻게 할지가 훨씬 어려웠다. 값이 수백만 건이라 한 번에 바꾸는 것도 간단하지 않았다.

Table of contents

Open Table of contents

선택지 — 세 가지 방식

선택지는 셋이었다.

1. 전부 다시 암호화한다
2. 읽을 때 옛것이면 새 방식으로 다시 저장한다
3. 옛것은 그대로 두고 읽을 때만 옛 방식으로 푼다

1은 member 가 424만 행이라 한 번에 UPDATE 하면 그동안 서비스가 느려지고, 2는 안 읽히는 행이 영영 안 바뀌며, 3은 decryptV1 을 영원히 유지해야 한다. 그래서 둘째를 기본으로 하고 접근이 드문 값만 cron 배치로 처리하는 방식을 골랐다.

어느 방식인지 표시하기

두 방식이 섞여 있으려면 값마다 어느 방식으로 암호화됐는지 알아야 했다. 풀어 보고 실패하면 다른 방식으로 시도하는 방법도 있지만 그것은 느리고 불확실하다.

그래서 값 앞에 v2: 접두를 붙이고 접두가 없으면 옛것으로 보게 했다.

public function decrypt(string $stored): string {
    if (str_starts_with($stored, 'v2:')) {
        return $this->decryptV2(substr($stored, 3));
    }
    return $this->decryptV1($stored);   // 접두 없는 것은 v1
}

str_starts_with 하나로 갈리므로 풀어 보고 실패하면 다른 방식으로 시도할 필요가 없다.

진행 상황을 셌다

두 방식이 섞여 있는 상태가 언제 끝나는지도 알아야 해서 접두별 건수를 매일 남기게 했다.

SELECT SUM(phone_enc LIKE 'v2:%')     AS v2,
       SUM(phone_enc NOT LIKE 'v2:%') AS v1,
       COUNT(*)                       AS total
FROM member;

v2 120만 · v1 304만 · total 424만으로 시작했고 하루 약 40만 건이 바뀌어 8일이면 끝난다는 계산이 나왔다. 자주 읽히는 행은 조회 함수가 encryptV2UPDATE 하며 바꾸고, 안 읽히는 행은 cron 배치가 NOT LIKE 'v2:%' 로 2,000건씩 끊어 가져가 처리했다. 이런 숫자가 없으면 언제 끝날지를 계속 짐작만 하고 있게 된다.

옛 방식을 언제 지울지

전부 옮겨지면 옛 방식을 지울 수 있지만 백업에서 복원하면 옛 방식으로 저장된 값이 다시 나타나므로 바로 지울 수는 없었다.

그래서 백업 보관 기간이 지난 뒤로 지우는 시점을 잡았다. 옛 방식에 쓰던 키도 그 시점까지는 함께 남겨야 했다. 지우는 조건과 시점을 미리 적어 두지 않으면 옛 코드가 영원히 남는다.

경계 값으로 확인하기

실제로 바꾸기 전에 빈 값과 특수 문자가 든 값과 아주 긴 문자열을 각각 넣어 가며 미리 시험해 봤다.

빈 문자열에서 한 번 걸렸는데 옛 방식은 그것을 그대로 두고 새 방식은 무언가를 만들어 냈다. 그렇게 되면 원래 빈 문자열이었는지 아닌지가 구분되지 않는다. 암호화된 값을 WHERE에 넣어 찾던 자리도 grep으로 전부 찾아 고쳐야 했다.

정리


Share this post on:

Previous Post
검사 통과가 완성이 아니다
Next Post
허용만 적어 둔 목록은 아무것도 막지 않았다