내 공개키는 내가 만들었는데, 왜 CA가 보증해야 하죠?

2024-12-217

TL;DR

  • 공개키만으로는 통신 상대방의 신원을 확인할 수 없어 '중간자 공격'에 취약합니다.
  • 신뢰할 수 있는 제3자 기관(CA)이 공개키의 주인을 보증하는 '디지털 인증서'를 발급하여 신뢰를 부여합니다.
  • 이처럼 인증서를 통해 신뢰를 구축하고 관리하는 전체 시스템을 PKI(공개키 기반 구조)라 하며, 브라우저의 자물쇠 아이콘이 대표적인 결과물입니다.

들어가며

비대칭키를 배우면 ‘공개키/개인키 한 쌍이면 끝’이라 생각하기 쉽습니다.

하지만 결제 데이터를 외부로 보낼 때 가장 먼저 부딪히는 질문은 이겁니다. “내가 받은 이 공개키가 정말 그 서버 것임을 누가 보장하지?” 암호화는 해주지만 ‘신원 확인’은 별개의 문제입니다.

이 간극을 메우는 장치가 바로 디지털 인증서(X.509)이고, 이를 운영하는 인프라가 PKI입니다.

이 공개키, 정말 믿을 수 있을까?

암호화의 기본은 대칭키와 비대칭키입니다. 대칭키 는 암호화 복호화에 같은 키를 쓰는 빠르고 단순한 방식이지만, 키를 상대방에게 안전하게 전달해야 하는 치명적인 약점이 있습니다.

그래서 등장한 것이 비대칭키(공개키/개인키) 방식입니다.

데이터를 받을 수신자가 한 쌍의 키를 만들어, 암호화에 사용할 공개키는 모두에게 나눠주고 복호화에 필요한 개인키는 자신만 안전하게 보관하는 방식이죠. 키를 전달할 필요가 없으니 훨씬 안전해 보입니다.

"클라이언트 입장에서 서버의 공개키를 받아서 중요한 데이터를 암호화해야 한다.
그런데 지금 내 손에 들어온 이 공개키 파일이 정말로 내가 통신하려는 그 서버의 것이라고 어떻게 확신할 수 있지?
만약 해커가 중간에서 자신의 가짜 공개키를 슬쩍 바꿔치기했다면 ?"

이게 바로 '중간자 공격(Man-in-the-Middle Attack)' 이라는 공격 기법입니다.

클라이언트는 가짜 공개키인 줄도 모르고 열심히 데이터를 암호화해서 보낼 테고, 공격자는 그걸 자기 개인키로 유유히 열어보는 끔찍한 상황이었습니다. 모든 정보가 그대로 노출되는 거죠.

이 신뢰의 문제를 해결하기 위해 등장한 것이 바로 디지털 인증서(Digital Certificate) 입니다.

신뢰의 이름표, 디지털 인증서 (X.509 Certificate)

디지털 인증서는 공개키에 대한 공인된 '신원 보증서' 라고 할 수 있습니다. 이 신원 보증서는 아무나 발급할 수 없고, 모두가 신뢰하기로 약속한 제3자 기관인 CA(Certificate Authority, 인증 기관) 만이 발급할 수 있습니다.

인증서가 만들어지는 과정은 이렇습니다.

  1. 서버는 자신의 공개키와 신원 정보(도메인 주소 등)를 담아 CA에 CSR(Certificate Signing Request, 인증서 서명 요청) 을 보냅니다. 이 요청서에는 "이 공개키의 주인은 바로 나"라는 것을 증명하기 위해 자신의 개인키로 만든 전자서명이 포함됩니다.
  2. CA는 서버의 신원을 여러 방법으로 검증하고, CSR이 유효하다고 판단되면 CA 자신의 개인키로 디지털 서명을 하여 공식적인 인증서를 발급해 줍니다.
  3. 이 인증서 안에는 서버의 공개키 + 서버의 신원 정보 + CA의 서명 + 유효 기간 등 이 모두 담기게 됩니다.

이제 클라이언트는 서버로부터 그냥 공개키가 아닌, 이 인증서를 전달받습니다. 그리고 자신의 컴퓨터(OS나 브라우저)에 이미 내장된 CA의 공개키를 이용해 인증서의 서명을 검증합니다.

서명이 유효하다면, "아, 이 인증서에 담긴 공개키는 위조되지 않았고, 신뢰할 수 있는 CA가 보증하는 진짜 서버의 것이 맞구나!"라고 확신할 수 있게 되는 것입니다.

그럼 PKI는 무엇일까?

방금 '신뢰'를 구축하기 위해 CA, CSR, 디지털 인증서 같은 요소들이 어떻게 작동하는지 살펴봤습니다.

PKI(Public Key Infrastructure, 공개키 기반 구조) 는 바로 이 모든 것을 포괄하는 거대한 '시스템' 또는 '인프라' 그 자체를 의미합니다.

  • 인증서를 만들고(CA)
  • 인증서를 요청하고(CSR)
  • 발급된 인증서를 사용하고 검증하며(신뢰의 사슬)
  • 필요하면 인증서를 폐기하는(CRL/OCSP)

이 모든 과정에 필요한 기술, 정책, 절차, 하드웨어, 소프트웨어 등을 총칭하는 용어가 바로 PKI입니다.

즉, 인증서는 PKI라는 거대한 신뢰 시스템이 만들어내는 핵심 '결과물'이고, CA는 이 시스템의 핵심 '구성원'인 셈입니다.

우리가 인터넷 뱅킹을 하거나 https 사이트에 접속할 때, 우리는 모두 이 PKI라는 거대한 사회적 약속 위에서 안전하게 통신하고 있는 것입니다

이론과 실무의 연결고리 : 코드와 브라우저에서 마주친 인증서

개념은 이제 알겠습니다. 그렇다면 이 '인증서'라는 것을 우리는 실제 어디서 마주치게 될까요? 제 경험을 돌이켜보니 크게 두 군데였습니다.

바로 서버 간 통신 코드와 매일 사용하는 웹 브라우저입니다.

서버 간 통신: 코드로 만나는 인증서의 실체

모바일 결제 솔루션을 개발하다 보면, 저희 서버는 카드사나 글로벌 결제 네트워크사와 같은 외부 시스템과 매우 민감한 정보를 주고받아야 합니다.

이때 안전한 통신 채널을 만들기 위해 바로 이 인증서가 사용됩니다.

파트너사로부터 전달받은 PEM(텍스트) 형식의 인증서 데이터를 코드에서 다뤘던 기억이 생생합니다.

import java.security.PublicKey;
import java.security.cert.X509Certificate;

// ...

// 파트너사로부터 받은 PEM 형식의 인증서 문자열
String partnerCertificatePemData = "-----BEGIN CERTIFICATE-----\nMIIC... \n-----END CERTIFICATE-----";

// 이 PEM 데이터를 프로그램이 이해할 수 있는 객체로 변환합니다.
// (실제 코드는 라이브러리나 유틸 클래스를 사용하므로, 개념을 보여주기 위한 가상 코드입니다.)
X509Certificate partnerCertificate = cryptoProvider.getCertificateFromPem(partnerCertificatePemData);

// 이제 인증서 객체에서 가장 중요한 '공개키'를 추출할 수 있습니다.
PublicKey partnerPublicKey = partnerCertificate.getPublicKey();

// 이 공개키를 사용해서 파트너사에게 보낼 데이터를 안전하게 암호화합니다.
byte[] encryptedData = encrypt(sensitiveData, partnerPublicKey);

여기서 X.509가 바로 국제 표준 디지털 인증서 형식이었고, 자바의 X509Certificate 클래스가 코드 레벨에서 다루는 인증서의 실체였던 것입니다.

처음에는 그저 암호화에 필요한 키를 가져오는 과정이라고만 생각했는데, 이제는 이 코드가 PKI라는 거대한 신뢰 시스템 위에서 동작하고 있다는 것을 알게 되었습니다.

웹 브라우저: 매일 경험하는 '신뢰의 사슬'

인증서는 서버 개발자만 만나는 것이 아닙니다. 우리는 매일 웹 브라우저를 통해 인증서의 덕을 보고 있습니다. 주소창에 뜨는 자물쇠 아이콘이 바로 그 증거입니다.

호기심이 생겨 브라우저의 자물쇠 아이콘을 눌러 구글의 인증서 정보를 직접 열어봤습니다.

alt text

거기에는 '인증서 경로' 또는 '인증서 계층'이라는 흥미로운 정보가 있었습니다.

  • 인증서 계층
    • GTS Root R4 (최상위 인증 기관)
    • WE2 (중간 인증 기관)
    • *.google.com (구글 서버 인증서)

이것이 바로 최상위 CA → 중간 CA → 서버로 이어지는 '신뢰의 사슬(Chain of Trust)' 이었습니다. 브라우저의 역할은 이렇습니다.

  1. 구글 서버가 보낸 *.google.com 인증서의 서명을 확인합니다. "이건 WE2가 서명했네."
  2. WE2 인증서의 서명을 확인합니다. "이건 GTS Root R4가 서명했구나."
  3. 마지막으로, 자신의 '신뢰 저장소(Trust Store)'를 뒤져봅니다. "내 컴퓨터에 GTS Root R4가 신뢰할 수 있는 기관으로 등록되어 있나?"
  4. 등록이 되어 있다면, 모든 서명이 유효하다고 판단하고 자물쇠 아이콘을 띄워주는 것입니다.

GTS Root R4를 어떻게 알고 신뢰하는 걸까?

이것이 바로 PKI 신뢰 모델의 가장 중요한 핵심, '신뢰의 앵커(Trust Anchor)' 개념입니다.
신뢰는 무한히 거슬러 올라갈 수 없기에, 모든 신뢰가 시작되는 절대적인 출발점이 필요합니다.

최상위 인증 기관(Root CA)의 인증서가 바로 이 앵커 역할을 합니다.
이 인증서들은 우리가 인터넷에서 다운로드하는 것이 아니라, Windows나 macOS, Android 같은 운영체제를 설치할 때 이미 함께 설치됩니다.

마이크로소프트, 애플, 구글과 같은 OS 제조사들은 매우 엄격한 보안 감사를 통과한 신뢰할 수 있는 CA들의 목록을 만들어, 이들의 인증서를 OS에 미리 포함시켜 배포합니다.

이 특별한 저장 공간을 '루트 스토어(Root Store)' 라고 부릅니다.

결국 브라우저가 하는 검증은, 정부가 위조 방지된 공식 인감을 미리 지정해두면 우리가 그 인감만 보고 서류를 신뢰하는 것과 같습니다. `브라우저는 루트 스토어에 등록된 CA라면 '묻지도 따지지도 않고' 신뢰하며, 이로부터 이어진 모든 신뢰의 사슬을 검증하는 것입니다.

결국 서버 코드에서 다루던 X509Certificate와 브라우저의 자물쇠 아이콘은, 표현 방식만 다를 뿐 PKI 라는 동일한 원리 위에서 우리의 통신을 안전하게 지켜주고 있었던 것입니다.

암호화 데이터의 구조와 형식: PKCS와 PEM

인증서의 원리를 이해하고 나니, 또 다른 질문이 생겼습니다. "인증서나 개인키 같은 민감한 정보는 컴퓨터에서 정확히 어떤 모습으로 저장되고, 시스템 간에 어떻게 교환될까?"

이 질문에 대한 답이 바로 PKCS와 PEM에 있었습니다. 이 둘은 각각 데이터의 '구조' 와 '형식' 을 정의하는 중요한 약속입니다

PEM (Privacy Enhanced Mail)

PEM은 우리가 가장 흔하게 마주치는 파일 형식입니다. 원래는 이름처럼 이메일 보안을 위해 만들어졌지만, 지금은 암호화 관련 데이터를 저장하고 전송하는 표준적인 텍스트 형식으로 널리 사용됩니다.

  • 역할: 바이너리(0과 1로 이루어진) 형태의 인증서나 키 데이터를, 어떤 시스템에서도 안전하게 복사하고 붙여넣을 수 있는 텍스트 문자열로 변환(인코딩) 하는 역할을 합니다.
  • 구조와 특징:
    • 인증서나 키 같은 암호화 데이터의 원본 바이너리 형식은 보통 DER(Distinguished Encoding Rules) 라는 표준에 따라 인코딩됩니다. DER은 데이터를 표현하는 하나의 바이너리 형식 규칙입니다.
    • PEM은 바로 이 DER 형식의 바이너리 데이터Base64 방식으로 인코딩하여 텍스트로 변환한 것입니다. 기술적으로 "PEM = Base64(DER)" 이라고 이해할 수 있습니다.
    • 그리고 이 텍스트 데이터의 시작과 끝을 -----BEGIN CERTIFICATE----- 와 -----END CERTIFICATE----- 같은 머리말과 꼬리말로 감싸, 내용물이 무엇인지 명확하게 알려줍니다.

결론적으로 PEM은, 암호화 데이터를 텍스트 기반의 환경(설정 파일, API 요청 본문 등)에서 쉽게 다루기 위한 '포장 형식' 입니다.

PKCS (Public-Key Cryptography Standards)

그렇다면 PEM 파일 안에 담기는 데이터의 내용물은 어떤 규칙으로 만들어질까요? 그 규칙을 정의한 것이 바로 PKCS입니다.

PKCS는 RSA Security 사에서 만든 공개키 암호 기술에 대한 산업 표준 규약 모음입니다.
즉, 파일 형식이 아니라 데이터의 논리적인 구조를 정의한 '설계도' 입니다

여러 PKCS 표준 중, 실무에서 자주 보이는 두 가지는 다음과 같습니다.

  • PKCS#8:

개인키(Private Key)의 구조를 정의하는 표준 입니다. 이 표준에 따르면, 개인키 정보에는 실제 키 데이터뿐만 아니라 이 키가 어떤 알고리즘(예: RSA, ECC)을 사용하는지에 대한 정보도 함께 포함되어야 합니다.
우리가 -----BEGIN PRIVATE KEY----- 라는 헤더를 가진 PEM 파일을 본다면, 그 안의 내용은 이 PKCS#8 표준에 따라 구성되어 있을 가능성이 높습니다.

  • PKCS#12:

개인키와 그에 맞는 인증서, 그리고 상위 CA 인증서 체인까지 하나로 묶는 방법을 정의하는 표준입니다.
보통 암호로 보호되는 바이너리 파일(.p12 또는 .pfx 확장자) 형태로 만들어지며, '디지털 금고'처럼 관련된 모든 정보를 안전하게 보관하고 한 번에 전달하는 데 사용됩니다.

PEM은 데이터를 어떻게 포장해서 보여줄지(파일 형식) 를 정의하고, PKCS는 그 포장 안에 담길 데이터의 내용물을 어떻게 구성할지(구조 표준) 를 정의합니다.

실무에서 마주친 두 가지 방식: PEM과 PKCS#12

이론을 알고 나니, 과거에 작성했던 코드가 새롭게 보이기 시작했습니다. 흥미롭게도 저희 프로젝트에서는 통신하는 대상에 따라 두 가지 방식을 모두 사용하고 있었습니다.

Visa와의 통신: PEM 형식 활용

Visa와 통신하며 데이터를 암호화할 때는, properties 파일에 저장된 PEM 형식의 인증서 문자열을 읽어와 공개키를 추출했습니다

// Visa의 암호화용 공개키가 담긴 PEM 형식의 문자열
String visaEncryptionPublicKeyPemData = "..."; // 설정 파일 등에서 읽어온 값

// PEM 데이터에서 X.509 인증서 객체를 생성
X509Certificate x509Certificate = visaCrypto.getCertificate(visaEncryptionPublicKeyPemData);

// 인증서에서 최종적으로 암호화에 사용할 공개키를 추출
visaEncryptPublicKey = (RSAPublicKey) x509Certificate.getPublicKey();

이 방식은 텍스트 기반이라 설정 파일이나 클라우드 환경에 유연하게 통합할 수 있다는 장점이 있습니다.

⚠️ 예시에서는 코드의 가독성을 위해 문자열 변수로 표현했지만, 실제 운영 환경(Production)에서는 보안을 위해 PEM 데이터나 비밀번호 같은 민감 정보를 application.properties 파일에 직접 저장하는 것은 매우 위험합니다. 소스 코드에 민감 정보가 그대로 노출될 수 있기 때문입니다.


Mastercard와의 통신: PKCS#12 형식 활용

반면 Mastercard와 통신할 때는, 암호화된 .p12 파일을 직접 로드해서 개인키를 가져왔습니다.

// .p12 파일을 다루기 위한 자바의 KeyStore 객체
KeyStore pkcs12KeyStore = KeyStore.getInstance("PKCS12");

// 파일 경로와 비밀번호를 이용해 .p12 파일 로드
// (파일 경로와 비밀번호 역시 외부 Secret 저장소에서 가져오는 것이 안전합니다.)
pkcs12KeyStore.load(new ClassPathResource(pkcs12KeyFilePath).getInputStream(), signingKeyPassword.toCharArray());

// KeyStore에서 지정된 별칭(alias)과 비밀번호로 개인키를 추출
clientEncryptionPrivateKey = (PrivateKey) pkcs12KeyStore.getKey(signingKeyAlias, signingKeyPassword.toCharArray());

PKCS#12 방식은 필요한 모든 정보(개인키, 인증서 등)를 하나의 암호화된 파일로 안전하게 관리하고 전달할 수 있다는 점에서 이점이 있습니다.

이처럼 어떤 방식이 절대적으로 좋다기보다는, 각 시스템의 정책과 지향점에 따라 다른 표준을 채택한다는 점이 매우 흥미로웠습니다.

결국 두 방식 모두 PKI라는 큰 틀 안에서 안전한 통신을 구현하기 위한 서로 다른 접근법인 셈입니다.

맺으며

우리가 만드는 결제 솔루션의 핵심은 결국 '신뢰'입니다. 그리고 그 신뢰는 바로 이런 탄탄한 암호 기술 위에서 만들어진다는 것을 다시 한번 느끼며, 앞으로 더 책임감을 갖고 코드를 작성해야겠습니다.

© 2025 박건희