Skip to Content
증명서 발급 (전자증명서)

증명서 발급 (전자증명서)

주민등록등본, 주민등록초본, 가족관계증명서를 간편인증으로 발급하는 기능입니다. HYPHEN과 CODEF 두 벤더를 선택적으로 사용할 수 있는 멀티 벤더 구조(v2)로 운영됩니다.

전체 흐름

사용자 입력 → 프론트 암호화 → 서버 복호화 → 벤더 규격 재암호화 → 벤더 API
  1. 사용자가 주민등록번호, 주소 등 민감 정보를 입력
  2. 프론트에서 얼마집 전용 AES-256 키로 암호화하여 서버에 전송
  3. 서버에서 복호화 후, 선택된 벤더(HYPHEN 또는 CODEF) API 규격에 맞게 재암호화
  4. 벤더를 통해 간편인증 요청 → 사용자 승인 → 증명서 PDF 발급

2단계 인증 흐름

모든 벤더는 간편인증이 필요하므로 2회의 API 호출로 증명서가 발급됩니다.

[1차 요청] 간편인증 요청 프론트 → POST /v2/user/certificate/resident-certificate { vendor, birthDate, identityEnc, loginOrgCd, sido, sigg } 서버 ← { stepData: "..." } ← HYPHEN인 경우 { twoWayInfo: { ... } } ← CODEF인 경우 [사용자가 휴대폰에서 간편인증 승인] [2차 요청] 증명서 발급 프론트 → POST /v2/user/certificate/resident-certificate { ..., stepData: "..." } ← HYPHEN인 경우 { ..., twoWayInfo: { ... } } ← CODEF인 경우 서버 ← { preSignedUrl: "https://s3..." }

벤더에 따라 인증 데이터 형식이 다릅니다:

벤더1차 응답2차 요청에 포함
HYPHENstepData (string)stepData
CODEFtwoWayInfo (object)twoWayInfo

암호화

얼마집 전용 키 (CERTIFICATE_AES_KEY)

프론트에서 민감 데이터를 암호화할 때 사용하는 AES-256-CBC 키입니다.

  • 알고리즘: AES-256-CBC
  • IV: 랜덤 16바이트
  • 출력 형식: Base64(IV + 암호문)
  • 환경변수: NEXT_PUBLIC_CERTIFICATE_AES_KEY
import {encryptCertificateField} from '@howmuchhome-web/utils-ts'; // 주민등록등본/초본: 주민번호 뒷자리만 암호화 const identityEnc = encryptCertificateField(birthPassword); // 7자리 // 가족관계증명서: 주민번호 뒷자리만 암호화 const rrn2Enc = encryptCertificateField(birthPassword); // 7자리

v1과 v2 암호화 차이

v1v2
알고리즘AES-128-CBCAES-256-CBC
ENCRYPTION_KEY (16바이트)NEXT_PUBLIC_CERTIFICATE_AES_KEY (32바이트)
IVuserId 기반 고정 IV랜덤 IV (매 호출 다름)
등본/초본encryptField(birth + birthPassword) 전체 암호화birthDate 평문 + encryptCertificateField(birthPassword)
가족관계rrn1Enc, rrn2Enc 모두 암호화rrn1 평문 + rrn2Enc 뒷자리만 암호화

v2에서는 주민번호 앞자리(생년월일)를 평문으로, 뒷자리만 암호화합니다. 서버에서 벤더별 규격에 맞게 재암호화하기 때문입니다.

API 엔드포인트

주민등록등본

POST /v2/user/certificate/resident-certificate

주민등록초본

POST /v2/user/certificate/resident-abstract

가족관계증명서

POST /v2/user/certificate/family-relations

요청 — 주민등록등본/초본

type CreateResidentCertificateV2Request = { vendor: CertificateVendor; // 'HYPHEN' | 'CODEF' loginOrgCd: string; // 간편인증 서비스 코드 (예: 'kakao', 'toss') birthDate: string; // 주민번호 앞 6자리 (평문) identityEnc: string; // 주민번호 뒷 7자리 (AES-256 암호화) sido: string; // 시/도 (예: '서울특별시') sigg: string; // 시/군/구 (예: '강남구') stepData?: string | null; // HYPHEN 2단계 인증 데이터 (2차 요청 시) twoWayInfo?: TwoWayInfo | null; // CODEF 2단계 인증 데이터 (2차 요청 시) assignmentId?: string | null; // 과제 ID (과제에서 진입 시) };

요청 — 가족관계증명서

type CreateFamilyRelationsV2Request = { vendor: CertificateVendor; loginOrgCd: string; rrn1: string; // 주민번호 앞 6자리 (평문) rrn2Enc: string; // 주민번호 뒷 7자리 (AES-256 암호화) fthrNm?: string | null; // 부 성명 mthrNm?: string | null; // 모 성명 sposNm?: string | null; // 배우자 성명 childNm?: string | null; // 자녀 성명 (가족 관계 최소 1개 필수) stepData?: string | null; twoWayInfo?: TwoWayInfo | null; assignmentId?: string | null; };

응답 (공통)

// 1차 응답: 간편인증 대기 { residentCertificate: { // 또는 residentAbstract, familyRelationsCertificate preSignedUrl: null, stepData: "...", // HYPHEN twoWayInfo: null, } } // 또는 { residentCertificate: { preSignedUrl: null, stepData: null, twoWayInfo: { // CODEF jobIndex: 0, threadIndex: 0, jti: "...", twoWayTimestamp: 1234567890 }, } } // 2차 응답: 발급 완료 { residentCertificate: { preSignedUrl: "https://s3...signed-url", stepData: null, twoWayInfo: null, } }

벤더 (CertificateVendor)

enum CertificateVendor { HYPHEN = 'HYPHEN', CODEF = 'CODEF', }

벤더 값은 과제(assignment) 데이터의 certificateVendor 필드에서 내려옵니다. 프론트에서는 이 값을 queryString으로 전달받아 사용합니다.

  • 과제에서 진입: AssignmentPage → queryString vendor → apply page
  • 전자증명서 메뉴에서 직접 진입: 기본값 CertificateVendor.HYPHEN

간편인증 서비스 (loginOrgCd)

코드서비스HYPHENCODEF
kakao카카오톡OO
naver네이버OO
toss토스OO
kbKB인증서OO
shinhan신한인증서OO

CODEF에서 지원하지 않는 loginOrgCd(kakaobank, payco 등)로 요청 시 에러가 반환됩니다.

프론트엔드 구현

파일 구조

apps/app/src/app/(certified-user-only)/more/electronic-certificate/ ├── page.tsx # 증명서 목록 (발급/제출 현황) ├── _components/ │ └── CertificateConsentBottomSheet.tsx ├── apply/ │ ├── page.tsx # 발급 퍼널 진입점 │ ├── funnel.ts # 퍼널 스텝 정의 │ ├── type.ts # 폼 타입 (BaseCertificateForm, ResidentRegistrationForm, FamilyRelationsForm) │ ├── _component/ │ │ └── RegionSelect.tsx # 시/도, 시/군/구 선택 │ └── section/ │ ├── ElectronicCertificateApplyPageIntro.tsx # Step 1: 정보 입력 │ ├── ResidentRegistrationForm.tsx # 등본/초본 입력 폼 │ ├── FamilyRelationsForm.tsx # 가족관계 입력 폼 │ ├── ElectronicCertificateApplyPageSimpleVerification.tsx # Step 2: 간편인증 선택 (1차 API) │ └── ElectronicCertificateApplyPageConfirm.tsx # Step 3: 인증 완료 (2차 API)

퍼널 흐름

intro → simpleVerification → confirm
스텝역할API 호출
intro주민번호/주소/가족관계 입력, 암호화없음
simpleVerification간편인증 서비스 선택1차 요청 (간편인증 요청)
confirm인증 안내 화면, 완료 버튼2차 요청 (증명서 발급)

폼 타입

// 공통 필드 type BaseCertificateForm = { vendor: CertificateVendor; loginOrgCd: EasyLoginCode; preSignedUrl?: string | null; stepData?: string | null; // HYPHEN 인증 데이터 twoWayInfo?: TwoWayInfo | null; // CODEF 인증 데이터 }; // 주민등록등본/초본 type ResidentRegistrationForm = BaseCertificateForm & { birthDate: string; // 주민번호 앞 6자리 (평문) identityEnc: string; // 주민번호 뒷 7자리 (암호화) sido: string; sigg: string; }; // 가족관계증명서 type FamilyRelationsForm = BaseCertificateForm & { rrn1: string; // 주민번호 앞 6자리 (평문) rrn2Enc: string; // 주민번호 뒷 7자리 (암호화) fthrNm: string; mthrNm: string; sposNm: string; childNm: string; };

API 호출 예시

// 1차 요청 (simpleVerification 스텝) const res = await UserAPI.createMyResidentCertificateV2({ vendor: form.vendor, loginOrgCd: selectedLogin, birthDate: form.birthDate, identityEnc: form.identityEnc, sido: form.sido, sigg: form.sigg, }); // → res.stepData 또는 res.twoWayInfo를 폼에 저장 // 2차 요청 (confirm 스텝) const res = await UserAPI.createMyResidentCertificateV2({ vendor: form.vendor, loginOrgCd: form.loginOrgCd, birthDate: form.birthDate, identityEnc: form.identityEnc, sido: form.sido, sigg: form.sigg, stepData: form.stepData, // HYPHEN이면 값이 있음 twoWayInfo: form.twoWayInfo, // CODEF이면 값이 있음 }); // → res.preSignedUrl로 PDF 다운로드 가능

환경 설정

환경변수용도비고
NEXT_PUBLIC_CERTIFICATE_AES_KEY얼마집 전용 AES-256 암호화 키 (32바이트)각 환경(dev/prod)마다 다른 값

주요 패키지/파일 참조

패키지/파일역할
packages/api/lib/user/schema.tsv2 요청/응답 타입 정의
packages/api/lib/user/index.tsv2 API 엔드포인트 (createMyResidentCertificateV2 등)
packages/api/lib/assignment/schema.tsCertificateVendor enum, AssignmentDto.certificateVendor
packages/utils-ts/aes-encryption.tsencryptCertificateField() — AES-256-CBC 암호화
apps/app/src/routes/index.tselectronicCertificateApply 라우트 (queryString: type, vendor)
Last updated on