들어가기 전에.

유저를 어떻게 식별하여 중복 가입을 막을 수 있을까?

그리고 어떻게 조금은 더 안전하게 관리할 수 있을까?

현재 기획이 완료되어 개발중인 프로젝트인 “Re-Use“에는 유저 1명이 1개의 계정만을 가질 수 있어야 한다.

블록체인과 모바일 신분증을 이용하여 인증하기 때문에 그렇게 어렵지 않을 것이라 생각했지만.

  1. UX 관점: 사용자에게 별다른 추가 인증 과정 없이, 모바일 신분증 인증만으로 간편한 로그인/회원가입 경험을 제공해야 함
  2. 로그인/회원가입 로직: 모바일 신분증 인증 후 반환되는 “유저의 고유한 값”을 통해, 기존에 등록된 사용자라면 로그인을, 새로운 사용자라면 회원가입을 진행하는 방식으로 플로우를 구성.

이런 과정 속에서, 어떻게, 어떤 정보를 사용해야 유저를 식별할 수 있는 고유한 값을 만들 수 있을까? 라는 고민이 시작되었다.

이전에 논의된 내용은 다음과 같다.

  1. 주민번호와 이름을 조합 후 해싱하여 사용
  2. 이메일 주소를 활용 - 이 경우 여러개의 메일이 있는 경우 바로 깨질 수 있는 전제라서 기각

그러던 중 인증의 반환값에서 CI 와 DID를 반환하는 사실을 알게 되었다.

CI 그리고 DID

CI

  • 개념: CI는 한국의 본인확인기관(예: 나이스평가정보, 코리아크레딧뷰로 등)에서 주민등록번호를 기반으로 생성하는 연계 정보
  • 특징:
    • 고유성: 특정 주민등록번호에 대해 단 하나의 CI 값만이 부여됨. 즉, 어떤 본인확인 수단(휴대폰 본인인증, 아이핀, 모바일 신분증 등)을 사용하더라도, 동일한 사용자는 항상 동일한 CI 값을 반환받음.
    • 비복호화성: CI는 주민등록번호로부터 암호화되어 생성되며, 이를 통해 주민등록번호 자체를 복호화할 수 없음. 이는 주민등록번호 수집 없이 개인이 본인임을 증명할 수 있게 하여 개인정보 보호에 기여.
    • 활용: 국내 서비스에서 사용자를 유니크하게 식별하고, 중복 가입을 방지하는 가장 보편적이고 신뢰성 높은 주요 식별자로 활용.

DID

  • 개념: User Decentralized Identifier는 블록체인 등 분산원장기술(DLT)을 기반으로 하는 사용자의 분산 식별자.
  • 특징:
    • 주권성 및 유일성: 중앙 기관의 통제 없이 사용자가 직접 생성, 소유, 관리할 수 있으며, 전 세계적으로 유일성을 가짐.
    • 미래 지향적: 웹 3.0 시대의 핵심 기술로, 사용자가 자신의 신원 정보를 온전히 통제하고 필요한 정보만 선택적으로 제시할 수 있는 자기 주권 신원(Self-Sovereign Identity)의 기반이 됨.
    • OmniCX 환경에서의 DID: OmniCX를 통해 발급되는 DID는 주로 did:kr:mobileid와 같은 특정 Method를 가지며, 이는 해당 모바일 신분증 앱과 연동되어 유일하게 관리됨. 저희 프로젝트에서는 이 특정 DID Method만 허용하여 사용자를 유니크하게 식별하는 보조 기준으로 활용.

어떻게 처리할까?

사용자 식별을 위한 CIDID 값을 안전하고 효율적으로 처리하기 위한 몇 가지 방안을 고려했다.

  1. 클라이언트(브라우저) 내부 해싱 처리:
    • 클라이언트 단에서 CI를 직접 해싱하는 방식.
    • 단점: bcrypt와 같은 안전한 해싱 알고리즘은 연산 비용이 높아 클라이언트 브라우저에서 직접 수행하기에는 성능 저하가 발생할 수 있음. 또한, 민감한 CI 값이 클라이언트 JavaScript 코드에 노출될 위험이 있다.
  2. Next.js API Routes (SSR 프론트엔드 서버 API) 활용 해싱 처리:
    • Re-Use 프로젝트는 Next.js 기반의 SSR(Server-Side Rendering) 아키텍처를 사용하기에 Next.js의 API Routes는 Node.js 환경에서 실행되는 서버 사이드 코드이므로, 이 곳에서 CI 해싱과 같은 민감한 연산을 안전하게 수행할 수 있음.
    • 장점: CI 값이 클라이언트(브라우저)에 노출되지 않고 서버 환경에서 안전하게 처리됨. bcrypt와 같은 고비용 해싱 연산도 서버 자원을 활용하므로 클라이언트 성능에 영향을 주지 않음.
    • 선택 배경: 클라이언트 단에서의 보안 및 성능 문제를 해결하고, 백엔드 서버의 부담을 줄이면서도 CI 해싱을 통합 관리할 수 있는 가장 적합한 방법이라고 판단.
  3. 백엔드(BE) 서버에게 전적으로 위임하기:
    • 민감 데이터를 처리하는 가장 전통적이고 안전한 방법.
    • 장점: 백엔드 서버는 일반적으로 높은 수준의 보안을 유지하며, 데이터베이스와의 직접적인 상호작용에 특화.
    • 단점: Re-Use 프로젝트의 현재 아키텍처와 개발 효율성을 고려했을 때, ci 값 해싱을 위해 추가적인 백엔드 API를 구현하고 관리하는 것이 초기에는 다소 비효율적일 수 있음. (물론 장기적으로는 백엔드 위임이 더 견고할 수 있다.)

위 세 가지 방안 중 Next.js API Routes를 활용한 해싱 처리 방식 (2번)을 선택했다.

CI 값 처리: 강력한 해싱 (bcrypt)

CI 값을 데이터베이스에 저장할 때는 복호화가 불가능한 해싱(Hashing) 방식을 사용. 이는 원본 CI 값이 데이터베이스 유출 시에도 직접적으로 노출되지 않도록 하여 보안성을 확보한다.

bcrypt 알고리즘을 사용한다. bcrypt는 다음과 같은 장점 때문에 패스워드 해싱에 널리 사용되며 CI 값에도 적합하다고 판단했다.

  • 높은 연산 비용 (Cost Factor): 의도적으로 해싱 연산을 느리게 하여 무차별 대입 공격(Brute-force Attack)의 효율성을 크게 낮춤.
  • 솔트(Salt) 자동 적용: 각 해싱 연산에 고유한 임의의 솔트를 자동으로 추가하여 레인보우 테이블 공격(Rainbow Table Attack)을 방지.

userDid 값 처리: 원본 그대로 저장

추후 구현 플로우

  1. API Route에서 OmniCX 인증을 파싱 후 유저에게 ci를 곧바로 해싱함
  2. 해당 결과를 백엔드 서버와 통신함
  3. 회원이 아니라면 회원 가입에 필요한 정보를 “안전하게” 서버에서 처리 후 회원가입을 마무리 해야함

결론

  • cibcrypt 해싱을 통해 안전하게 저장하여 중복 가입을 방지하고 사용자를 유니크하게 식별
  • userDid는 원본 그대로 저장.

Next.js API Routes를 활용하여 CI 해싱과 같은 민감한 처리 로직을 서버 사이드에서 수행함으로써 클라이언트 노출 위험을 최소화하고 성능을 확보하기 위해 노력하자!

아무래도 추후 구현 플로우의 3번이 꽤나 어렵지 않을까 생각이 들긴 한다.

클라이언트의 개입을 최소화하면서 동시에 UX를 생각하는 즐거운 고민의 시간이다.