개발 이야기3분 읽기

이 값을 로그에 남겨도 되나

이메일을 SHA-256으로 해싱해도 익명화가 아닙니다. 값별로 무엇이 드러나는지 실측과 함께 정리했습니다.

디버깅을 위해 값을 로그에 찍습니다. 그 로그는 대체로 오래 남고, 여러 사람이 보고, 외부 서비스로도 갑니다.

값별 판단

값                    판단        이유
JWT 전체              안 됨       페이로드가 그대로 읽히고 서명까지 있으면 재사용 가능
세션 ID               안 됨       로그를 읽으면 세션을 탈취한다
bcrypt 해시           안 됨       오프라인 대입 공격의 재료
API 키 전체           안 됨       그대로 쓸 수 있다
API 키 앞 8자         대체로 됨   키를 특정하되 복원은 못 하는 절충
UUID v4              됨          아무 정보도 담지 않는다
UUID v1 / v7 / ULID   주의        생성 시각이 드러난다
JWT 의 sub 클레임      대체로 됨   내부 ID. 그 자체가 개인을 특정하면 개인정보
이메일 주소            주의        개인정보
이메일의 SHA-256       주의        해시해도 복원된다

해시가 익명화가 아닌 이유

"개인정보를 해싱해서 로그에 남기면 안전하다"는 처리가 흔합니다. 재 봤습니다.

SHA-256 계산 속도    158만 회/초  (단일 코어)
유출 이메일 1억 건 훑기      63초

유출된 이메일 목록은 공개적으로 유통됩니다. 그 목록을 전부 해싱해서 로그의 해시와 대조하면 됩니다. 1억 건에 1분입니다.

전화번호는 더 짧습니다.

010-XXXX-XXXX 조합 수   1억 개
전수 계산                63초

목록조차 필요 없습니다. 가능한 모든 번호를 만들어 해싱하면 됩니다.

입력 공간이 좁으면 해시는 되돌아갑니다. 이메일과 전화번호가 정확히 그런 경우입니다. 비밀번호와 다른 점은 사용자가 고르는 값이 아니라는 것입니다 — 형식이 정해져 있고 후보가 열거 가능합니다.

그래도 해싱해야 한다면

느린 해시를 쓰고 솔트를 붙입니다.

SHA-256    1억 건에 63초
PBKDF2     1억 건에 76일   (60만 회 반복, 단일 코어)

10만 배 차이입니다. 다만 76일도 절대적인 안전은 아닙니다 — GPU와 병렬화를 쓰면 줄어듭니다.

솔트가 더 중요합니다. 서비스별로 다른 솔트를 쓰면 미리 계산된 표를 그대로 쓸 수 없고, 다른 서비스의 유출 데이터와 대조할 수도 없습니다.

가장 확실한 것은 원본과 무관한 식별자로 바꾸는 것입니다. 내부 사용자 ID나 UUID v4를 로그에 쓰고, 그 매핑을 별도 저장소에 두는 방식입니다.

시간이 드러나는 식별자

UUID v4는 무작위라 아무것도 담지 않습니다. 그런데 정렬 가능한 식별자는 다릅니다.

UUID v7   앞 48비트가 유닉스 밀리초
ULID      앞 10자가 타임스탬프
UUID v1   타임스탬프와 (구현에 따라) MAC 주소

계정 ID가 ULID이면 그 계정이 언제 만들어졌는지 누구나 알 수 있습니다. 대부분 문제가 아니지만, 가입 시각이 민감한 서비스도 있습니다.

UUID v1은 노드 필드에 MAC 주소가 들어가는 구현이 남아 있습니다. 그 경우 어느 기계에서 만들었는지까지 나갑니다.

로그가 가는 곳

로그를 어디까지 보내는지도 함께 봐야 합니다.

외부 로그 수집 서비스로 보내면 그 서비스의 보관 정책과 접근 권한이 적용됩니다. 오류 추적 도구는 예외 발생 시점의 변수를 통째로 담기도 합니다 — 요청 객체가 그대로 들어가면 헤더의 Authorization도 함께 갑니다.

프레임워크의 기본 마스킹 목록을 확인하는 것이 먼저입니다. 대체로 password·token·secret 같은 키 이름을 거르는데, 필드 이름이 다르면 걸러지지 않습니다.

확인 순서

  1. 오류 추적 도구가 요청 객체를 통째로 담는지 봅니다. 헤더가 함께 갑니다
  2. 마스킹 목록에 실제로 쓰는 필드 이름이 들어 있는지 확인합니다
  3. 개인정보를 해싱해서 남기고 있다면 그것을 익명화로 세지 않습니다
  4. 정렬 가능한 식별자를 외부에 노출하면 생성 시각이 함께 나갑니다
  5. 로그 보관 기간을 봅니다. 오래 남을수록 유출 시 영향이 큽니다

tools.onuel.dev에서 확인해 볼 수 있습니다. JWT 디코더에 토큰을 넣으면 페이로드가 키 없이 읽히는 것이 보이고, SHA-256에 같은 이메일을 두 번 넣으면 항상 같은 값이 나오는 것 — 대조가 가능하다는 뜻 — 을 확인할 수 있습니다. 느린 해시는 PBKDF2bcrypt 쪽이고, 시각을 담지 않는 식별자는 UUID v4입니다.

  • #로깅
  • #보안
  • #개인정보