[학습기록] CI와 DI의 차이점: 개인정보 식별자는 어떻게 관리해야 할까?
현재 회사로 이직하기 전까지 약 20년 동안 임베디드 소프트웨어와 소규모 기업용 시스템을 개발해왔습니다.
대부분 10명 이내의 제한된 사용자만 사용하는 시스템이었고, 사용자 계정도 직접 관리하다 보니 회원가입이나 본인인증 기능 자체가 없었습니다. 자연스럽게 개인정보를 다룰 기회도 거의 없었습니다.
그러다가 본인인증과 개인정보를 다루는 서비스를 운영하게 되면서 개인정보 식별자로 사용되는 CI와 DI라는 용어를 자주 접하게 되었습니다.
CI는 본인인증에 사용되는 식별자 정도로만 알고 있었고, DI는 서비스에서 자체적으로 정의한 값이라고만 생각했습니다. 제가 담당한 서비스에서는 DI라는 이름으로 UUID를 생성해 사용하고 있었기 때문입니다.
그러던 중 최근 주민등록번호와 CI의 분리 보관 시행일이 앞당겨진다는 소식을 계기로, 이에 대한 대응을 위해 정보보안팀과 관련 내용을 검토하면서 막연하게 알고 있었던 부분이 많다는 것을 깨달았습니다.
그래서 CI와 DI의 개념과 역할, 개인정보 식별자를 안전하게 관리하는 방법에 대해 공부하게 되었습니다.
1. CI(연계정보)와 DI(중복가입확인정보)의 차이
CI는 우리말로 연계정보(Connecting Information)라고 합니다. 여러 서비스나 시스템에서 동일인을 식별하기 위해 사용하는 정보입니다. 동일인이라면 서비스가 달라도 동일한 CI 값이 발급됩니다.
반면, DI는 중복가입확인정보(Duplication Information)로 하나의 서비스에서 동일인의 중복 가입 여부를 확인하기 위해 사용하는 정보입니다. 동일인이더라도 서비스마다 다른 값으로 발급됩니다.
| 구분 | CI | DI |
|---|---|---|
| 의미 | 연계정보 | 중복가입확인정보 |
| 생성 | 본인확인기관 | 본인확인기관 |
| 목적 | 동일인 연계 | 중복가입 확인 |
| 활용 | 서비스 간 동일인 식별 | 동일 서비스 내 중복가입 확인 |
flowchart TB
User["동일한 사용자"]
subgraph S1["서비스 A"]
ACI["CI: ABC123"]
ADI["DI: A-7F91"]
end
subgraph S2["서비스 B"]
BCI["CI: ABC123"]
BDI["DI: B-3C24"]
end
User --> S1
User --> S2
ACI -. "동일한 CI" .- BCI
ADI -. "서비스마다 다른 DI" .- BDI
style User fill:#f5f5f5,stroke:#555
style ACI fill:#e8f4ff,stroke:#3b82f6
style BCI fill:#e8f4ff,stroke:#3b82f6
style ADI fill:#fff4e5,stroke:#f59e0b
style BDI fill:#fff4e5,stroke:#f59e0b
2. 단순 식별자를 DI로 사용하면 생기는 문제
제가 운영을 맡았던 어떤 서비스에서는 본인확인기관에서 발급받은 DI가 아니라 아래와 같이 UUID를 생성해 DI 컬럼에 저장하고 있었습니다.
String di = UUID.randomUUID().toString();
본래 의미대로라면 중복가입확인정보로서 역할을 해야 했습니다. 하지만 이렇게 만들어 낸 DI는 사실상 서비스에서 자체적으로 정의한 식별자에 불과했습니다.
flowchart LR
User["동일한 사용자"]
Auth1["첫 번째 본인인증"]
Auth2["두 번째 본인인증"]
Data1["CI: ABC123<br/>DI: 6d1a..."]
Data2["CI: ABC123<br/>DI: b82f..."]
Result["DI가 매번 달라짐<br/>중복가입 확인 불가"]
User --> Auth1 --> Data1
User --> Auth2 --> Data2
Data1 --> Result
Data2 --> Result
style User fill:#f5f5f5,stroke:#555
style Data1 fill:#fff4e5,stroke:#f59e0b
style Data2 fill:#fff4e5,stroke:#f59e0b
style Result fill:#ffe8e8,stroke:#dc2626,stroke-width:2px
동일인이더라도 매번 DI가 달라지기 때문에 본래 목적인 중복가입확인정보로 사용할 수 없었습니다. 이로 인해 많은 결함이 서비스에 존재했고, 데이터의 정합성을 맞추기도 어려웠습니다.
또한, 결국 동일 회원 여부를 확인해야 하는 모든 기능은 CI를 이용해야 했습니다.
DI의 역할이 충분히 고려되지 않은 채 설계된 것으로 보였습니다. 결과적으로 DI를 단순 식별자로 사용하면서 이러한 문제가 발생했습니다.
이 경험을 통해 식별자는 값의 형태보다 어떤 역할을 수행해야 하는지가 더 중요하다는 점을 알게 되었습니다.
3. DI를 대신할 식별자 생성하기
CI는 여러 서비스에서 동일인을 식별하기 위한 정보이므로 모든 서비스에서 동일한 값이 보장되어야 합니다. 따라서 서비스가 자체적으로 생성할 수 없으며 본인확인기관에서 발급받아야 합니다.
하지만 DI는 하나의 서비스에서 동일인의 중복 가입 여부를 확인하기 위한 정보입니다. 직접 생성하는 것이 아니라 발급받아 사용하는 것이 원칙이지만, 서비스 내부에서 공식적인 DI를 대신할 식별자가 필요한 경우도 있습니다. 이때는 UUID처럼 매번 달라지는 값을 사용하는 것이 아니라, 동일한 사용자라면 항상 동일한 값이 생성되는 방식으로 식별자를 만드는 것이 중요합니다.
이러한 경우에는 서비스를 식별할 수 있는 값(예: 서비스ID, 애플리케이션 ID, 고객사 코드 등)과 CI를 이용해 동일한 입력에 대해서는 항상 동일한 결과가 생성되도록 식별자를 만드는 것이 좋습니다.
따라서 별도로 안전하게 관리하는 비밀키를 이용해 서비스 식별값 + CI를 HMAC으로 처리하면, 동일한 사용자에 대해서는 항상 동일한 식별자가 생성되면서도 서비스마다 서로 다른 식별자를 사용할 수 있습니다. 또한 여러 서비스 간에 내부 식별자를 비교해 동일인을 추적하는 것을 방지할 수 있으며, 애플리케이션에서 원본 CI를 직접 사용하는 범위를 줄일 수 있다는 장점도 있습니다.
flowchart LR
CI["CI<br/>ABC123"]
Key["비밀키<br/>(안전하게 별도 관리)"]
ServiceA["서비스 식별값<br/>SERVICE_A"]
ServiceB["서비스 식별값<br/>SERVICE_B"]
InputA["SERVICE_A | ABC123"]
InputB["SERVICE_B | ABC123"]
HMACA["HMAC-SHA256"]
HMACB["HMAC-SHA256"]
InternalA["서비스 A 내부 식별자<br/>a81f..."]
InternalB["서비스 B 내부 식별자<br/>7c42..."]
ServiceA --> InputA
CI --> InputA
InputA --> HMACA
Key --> HMACA
HMACA --> InternalA
ServiceB --> InputB
CI --> InputB
InputB --> HMACB
Key --> HMACB
HMACB --> InternalB
InternalA -. "서비스마다 다른 값" .- InternalB
style CI fill:#e8f4ff,stroke:#3b82f6
style ServiceA fill:#f5f5f5,stroke:#666
style ServiceB fill:#f5f5f5,stroke:#666
style HMACA fill:#ede9fe,stroke:#7c3aed
style HMACB fill:#ede9fe,stroke:#7c3aed
style InternalA fill:#eaf8ef,stroke:#16a34a
style InternalB fill:#eaf8ef,stroke:#16a34a
4. CI 보관 방법
CI는 개인을 식별할 수 있는 중요한 정보입니다. 따라서 관련 법령과 내부 보안 정책에 따라 안전하게 보호되어야 하며, 일반적으로 암호화하여 저장하는 것이 권장됩니다.
만약 서비스에서 CI를 동일인 식별 용도로만 사용하고, 원본 CI를 다시 조회하거나 다른 시스템에 제공할 필요가 없다면 HMAC을 이용해서 생성한 내부 식별자만 저장하는 방법도 고려할 수 있습니다.
flowchart LR
CI["원본 CI"]
Key["비밀키<br/>(별도 보관)"]
Encrypt["암호화"]
HMAC["HMAC-SHA256"]
EncryptedCI["암호화된 CI<br/>필요한 경우 보관"]
InternalID["내부 식별자<br/>일상적인 회원 식별에 사용"]
CI --> Encrypt --> EncryptedCI
CI --> HMAC --> InternalID
Key --> HMAC
style CI fill:#ffe8e8,stroke:#dc2626
style Key fill:#fff4e5,stroke:#f59e0b
style Encrypt fill:#e8f4ff,stroke:#3b82f6
style HMAC fill:#ede9fe,stroke:#7c3aed
style EncryptedCI fill:#fff4e5,stroke:#f59e0b
style InternalID fill:#eaf8ef,stroke:#16a34a
- 원본
CI대신 내부 식별자 활용 - 회원 식별 및 중복 확인 가능
- 내부 식별자만으로는 원본
CI를 직접 복원 불가
이러한 방식은 원본 CI를 직접 사용할 필요가 없는 서비스라면 개인정보 보호 측면에서 효과적인 대안이 될 수 있습니다. 물론 원본 CI를 외부 기관에 전달하거나 다시 조회해야 하는 업무가 있다면, 암호화된 CI를 함께 보관하는 방식이 더 적합할 수 있습니다.
5. 마무리
개인정보를 다루는 시스템을 설계할 때는 CI와 DI가 각각 어떤 역할을 수행하기 위해 존재하는지 이해하는 것이 중요합니다.
또한 원본 CI를 직접 사용할 필요가 없다면 내부 식별자를 활용하는 것이 개인정보 보호 측면에서 효과적인 선택이 될 수 있습니다.