CSR 클라이언트 서명키란
main
date
Sep 23, 2026
slug
csr-client-signing-key
author
status
Public
tags
summary
type
Post
thumbnail
category
🤖 Computer Science
updatedAt
Sep 23, 2026 01:42 AM
CSR(Certificate Signing Request)과 클라이언트 서명키(개인키/공개키 쌍)의 개념, 역할, 생성 방법을 정리한 노트입니다.
CSR 클라이언트 서명키란
CSR(Certificate Signing Request)은 PKI(공개키 기반시설)에서 인증기관(CA)에 "이 공개키로 인증서를 발급해달라"고 요청하는 메시지입니다(PKCS#10 표준, ASN.1 인코딩). "클라이언트 서명키"는 이 CSR을 만들 때 클라이언트가 미리 생성해두는 개인키/공개키 쌍을 가리키는 말입니다 — CSR 자체가 이 개인키로 서명되기 때문에 그렇게 부릅니다.
키가 하는 역할
요소 | 역할 |
개인키(private key) | ① CSR에 자체 서명(self-sign)해서 "이 공개키의 주인이 맞다"는 걸 증명(Proof of Possession) ② 인증서 발급 후에는 실제 통신(TLS 핸드셰이크 등)에서 서명·키 교환에 계속 사용됨. 절대 CA나 서버로 전송하지 않고 로컬에만 보관 |
공개키(public key) | CSR 안에 포함되어 CA로 전달됨. CA가 검증 후 발급하는 인증서에 그대로 박힘 |
CA 서명 | CA가 CSR의 신원(CN/O/OU 등)을 검증한 뒤, 공개키가 진짜 그 신청자 것임을 CA의 개인키로 보증. 이게 곧 "인증서"가 됨 |
즉 흐름은 다음과 같습니다.
1. 클라이언트: 키 쌍 생성 (개인키 보관, 공개키는 공개 가능) 2. 클라이언트: 공개키 + 신원정보(CN/O/OU/C…)를 담아 CSR 생성 → 개인키로 CSR에 서명 3. CA: CSR 검증 → 공개키를 인증서에 담아 CA 개인키로 서명 → 인증서 발급 4. 클라이언트: 인증서 + (원래 갖고 있던) 개인키를 한 쌍으로 사용
CSR 자체는 신원을 "증명"하지 않습니다 — 신원 검증은 CA가 별도 수단(CMP, EST, ACME, 서류 심사 등)으로 합니다. CSR은 어디까지나 "이 공개키/개인키 쌍은 내가 만들었다"는 것만 증명합니다.
어디서 이 키가 쓰이나 (실무 예시)
- mTLS(상호 TLS) 클라이언트 인증 — 서버가 자기 인증서만 보여주는 일반 TLS와 달리, 클라이언트도 인증서를 제시해야 하는 구조. 클라이언트가 CSR을 만들어 내부 CA에 서명받고,
extendedKeyUsage = clientAuth가 박힌 클라이언트 인증서를 받습니다. NestJS에서는https.createServer({ requestCert: true, ca: caCert })로 서버가 클라이언트 인증서를 검증하는 식으로 붙입니다.
- APNs(Apple 푸시) 인증서 — macOS Keychain Access의 "인증서 지원 요청"으로 CSR을 만들어 Apple Developer 포털에 업로드하면, Apple이 서명한 푸시용 인증서를 내려줍니다. 이 인증서 + 원래 만든 개인키를
.p12로 묶어 서버(NestJS 등)에서 APNs와의 TLS 연결에 사용합니다. (참고로 웹 푸시/VAPID는 CSR·CA 방식이 아니라 자체 서명 ES256 키쌍이라 이것과는 별개의 메커니즘입니다.)
- 일반 웹서버 SSL/TLS 인증서 — 가장 흔한 케이스로, 서버가 CSR을 만들어 공인 CA(Let's Encrypt 등)에 제출해 도메인용 인증서를 받는 경우입니다.
생성하는 방법
OpenSSL (가장 표준적인 방법)
# 1) 개인키 생성 (RSA 4096비트) openssl genrsa -out client.key.pem 4096 # 2) 개인키로 CSR 생성 (대화식으로 CN/O/OU/C 등 입력) openssl req -new -key client.key.pem -out client.csr # (mTLS 클라이언트 인증서라면) CA가 확장 필드를 지정해 서명 # client_ext.cnf # basicConstraints = CA:FALSE # keyUsage = critical, digitalSignature, keyEncipherment # extendedKeyUsage = clientAuth openssl ca -config openssl.cnf -extfile client_ext.cnf \ -in client.csr -out client.cert.pem
Country, State, Organization 필드는 서명할 CA 인증서와 일치해야 하는 경우가 많습니다(policy match 실패 시 서명 거부).Node.js에서 프로그래밍적으로 생성
Node.js 내장
crypto 모듈은 키 쌍 생성(generateKeyPair)은 지원하지만 PKCS#10 CSR 포맷 자체는 지원하지 않습니다. 두 가지 방법이 실무에서 쓰입니다.child_process로 위 openssl 명령을 그대로 호출 (가장 간단, 서버에 openssl 필요)
- 순수 JS 라이브러리인 node-forge 사용
const forge = require('node-forge'); // 키 쌍 생성 const keys = forge.pki.rsa.generateKeyPair(2048); // CSR 생성 const csr = forge.pki.createCertificationRequest(); csr.publicKey = keys.publicKey; csr.setSubject([ { name: 'commonName', value: 'client.example.com' }, { name: 'organizationName', value: 'Instiz' }, { shortName: 'OU', value: 'Backend' }, { name: 'countryName', value: 'KR' }, ]); // 개인키로 서명 (proof of possession) csr.sign(keys.privateKey, forge.md.sha256.create()); const csrPem = forge.pki.certificationRequestToPem(csr); const privateKeyPem = forge.pki.privateKeyToPem(keys.privateKey); // csrPem → CA에 제출, privateKeyPem → 서버 환경변수/시크릿 매니저에만 보관
다루실 때 주의할 점
- 개인키는 VAPID 개인키·DB 자격증명과 동급으로 취급 — 로컬/스테이징/프로덕션 키를 절대 섞지 말 것
- CSR은 개인키가 유출돼도 재생성 가능하지만, 개인키 자체가 유출되면 인증서를 폐기(revoke)하고 처음부터 다시 해야 함
- 키 사용 목적에 따라 CSR/인증서의 확장 필드(
keyUsage,extendedKeyUsage)를 명확히 지정해야 함 — 서버 인증서용 키를 클라이언트 인증서 용도로 쓰면 검증 단계에서 거부될 수 있음