와일드카드 갱신 배포까지 30분, 스테이징 엔드포인트는 SSL_ERROR_BAD_CERT_DOMAIN을 던지고 있고, 터미널 스크롤백에 남아 있는 건 PEM 블록 하나뿐이다. 배포 윈도우가 닫히기 전에 SAN 목록과 만료 시각, SHA-256 지문을 봐야 한다. openssl x509 -text도 답을 주지만, 플래그 다섯 개를 친 뒤에도 정작 보고 싶은 한 줄을 찾아 스크롤하고 있다. 이런 순간에는 용도가 좁혀진 디코더가 더 빠르다.

지금 인증서 디코드 →

PEM 인증서 안에는 무엇이 들어 있나

.pem 파일은 DER 바이너리를 Base64로 감싼 것에 불과하다. -----BEGIN CERTIFICATE----------END CERTIFICATE----- 마커를 떼어내고 Base64 디코딩하면 ASN.1로 인코딩된 바이너리 구조가 나온다. LDAP 메시지, SNMP 패킷, 대부분의 공개 키 포맷이 쓰는 바로 그 인코딩이다.

최상위 구조는 RFC 5280이 고정해 둔다:

Certificate ::= SEQUENCE {
    tbsCertificate       TBSCertificate,
    signatureAlgorithm   AlgorithmIdentifier,
    signatureValue       BIT STRING
}

tbsCertificate(“to be signed”, 서명 대상)에 사람이 신경 쓰는 모든 필드가 들어 있다. 버전, 일련번호, 유효 기간, Subject, Issuer, Subject Public Key Info, 그리고 v3 확장 목록. signatureAlgorithmsignatureValue는 나머지 일을 맡는다. TBS 바이트가 해당 CA의 서명을 받았음을 증명한다.

ZeroTool 디코더는 평범한 JavaScript로 작성된 작은 ASN.1 파서로 이 트리 전체를 브라우저 안에서 걸어 다닌다. 업로드도, 서버 측 OpenSSL도, API 키도 필요 없다. cert-tools-online.example 같은 곳에 붙여 넣기 망설여지는 PEM이라도 여기서는 안전하게 붙일 수 있다.

유효 기간 읽기

notBeforenotAfter 타임스탬프는 2050년 이전이면 UTCTime(YYMMDDhhmmssZ), 그 이후면 GeneralizedTime(YYYYMMDDhhmmssZ)으로 인코딩된다. RFC 5280이 이 전환점을 강제한 이유는 두 자리 연도가 모호해지지 않도록 하기 위해서다.

현장에서 신경 쓰는 상태는 네 가지다:

상태의미처리
유효 (남은 기간 > 30일)기간 내, 갱신 윈도우 진입 전동작 불필요 — 모니터링 대시보드에 만료 일자를 등록
30일 이내 만료브라우저는 아직 수락, ACME · 상용 CA 갱신은 보통 이 시점에 시작로테이션을 예약
만료됨브라우저는 거부, 클라이언트는 NET::ERR_CERT_DATE_INVALID를 본다즉시 로테이션, 만료일이 최근이면 시계 편차를 확인
아직 유효하지 않음notBefore가 미래 — 시계 편차 또는 단계적 롤아웃서버 NTP와 CA 발급 시각을 확인

디코더는 이 상태들을 색상으로 직접 표시한다. 운영자가 가장 먼저 시선을 두는 곳은 Days Left 행이다. 30 이하가 보이면 대화는 곧장 “갱신 작업이 돌고 있는가?”로 옮겨 간다.

Subject vs Issuer vs SAN — 현대 호스트명 매칭의 진짜 규칙

2026년의 브라우저는 호스트명 매칭에서 Subject CN을 보지 않는다. Subject Alternative Name(SAN) 확장만 본다. Chrome은 58에서 CN 폴백을 제거했고, Firefox는 48, Safari는 iOS 13. CN=example.com만 있고 SAN이 없는 인증서는 모든 주요 브라우저가 거절한다.

그래서 인증서를 디코드할 때는 이 순서로 읽는다:

  1. SAN — 이 인증서가 실제로 커버하는 호스트명은 어떤 것인가?
  2. Subject — CN이 일관성 있는가? (주로 사람용. CA는 여전히 채우는 관행)
  3. Issuer — 누가 서명했는가? (Let’s Encrypt R3, DigiCert Global Root, ZeroSSL, 또는 사내 CA 중 무엇을 예상해야 하는지가 결정된다)

오늘 전형적인 Let’s Encrypt 인증서는 SAN에 루트 도메인(example.com)과 www 서브도메인을 함께 담는다. 와일드카드 인증서는 *.example.com이 SAN이고, CN이 아니다. 디코더는 SAN 항목을 종류(DNS, IP, URI, email)별로 그룹화하므로 잘못 발급된 와일드카드를 빠르게 찾아낼 수 있다.

Key Usage와 Extended Key Usage는 “허가증”

Key Usage 확장은 9비트 BIT STRING으로, 이 인증서의 공개 키가 무엇을 할 수 있는지 선언한다. Extended Key Usage(EKU)는 그 위에 serverAuth, codeSigning 같은 의도 수준의 권한을 얹어 세분화한다.

TLS 서버 인증서에서 기대하는 조합:

Key Usage: digitalSignature, keyEncipherment
EKU      : serverAuth, clientAuth

CA 인증서(중간 또는 루트)에서 기대하는 조합:

Key Usage: keyCertSign, cRLSign
Basic Constraints: CA = true

리프 인증서에 keyCertSign이 켜져 있다면 뭔가 잘못된 것이다. 이 비트는 다른 인증서를 서명할 권한을 의미하며, 중간 CA만 가져야 할 특권이다. 디코더는 KU와 EKU를 읽기 쉬운 chip으로 보여주므로 한눈에 훑을 수 있다.

지문: 어떤 해시를 어디에 쓰는가

디코더의 지문은 인증서 전체 DER 바이트(PEM 텍스트도, 공개 키 단독도 아닌)에 대한 SHA-256, SHA-1, MD5 다이제스트다. 인증서의 일부가 아니다 — CA는 지문에 서명하지 않는다 — 하지만 특정 인증서 파일을 식별하는 가장 저렴한 방법이다.

해시2026년의 실제 용도
SHA-256현 시점의 기본 식별자. HSTS preload 제출, 모바일 앱 인증서 핀닝, 일부 표준의 SRI, openssl x509 -fingerprint -sha256이 전부 SHA-256을 가정한다.
SHA-1레거시 도구: 오래된 모니터링 스크립트, 마이그레이션이 끝나지 않은 사내 CMDB, 일부 IDS/IPS 시그니처. 새로운 핀닝 용도로는 사용 금지.
MD5아주 오래된 openssl 출력과의 호환. 암호학적으로 깨졌으므로 읽기 전용 대조 용도에만.

핀닝 용도라면 SHA-256 행을 복사한다. 누군가 로그에 적어 둔 해시를 grep으로 찾을 때, 디코더가 세 가지를 모두 보여주므로 상대가 어떤 것을 썼는지 추측할 필요가 없다.

RSA, ECDSA, Ed25519 — 공개 키 필드가 알려주는 것

Subject Public Key Info 섹션은 알고리즘 OID와 원시 키 바이트를 담는다. 현대에 만나게 되는 세 가지 형태:

1.2.840.113549.1.1.1  rsaEncryption          (고전적인 RSA)
1.2.840.10045.2.1     id-ecPublicKey         (이름 곡선 위의 ECDSA)
1.3.101.112           id-Ed25519             (edwards25519 위의 EdDSA)

RSA의 키 바이트는 자체적으로 다시 ASN.1 SEQUENCE { modulus, exponent }이다. 디코더는 modulus의 비트 길이를 보고한다. 2048은 허용 범위, 3072가 권장, 4096은 루트 CA 영역. 2026년에 1024비트 RSA를 본다면 그 자체가 finding이다. 주요 브라우저는 대략 2014년부터 수락을 중단했다.

ECDSA는 이름 곡선 OID를 표시한다. 실무에서 만나는 세 가지:

  • prime256v1(P-256 또는 secp256r1로도 불림) — Let’s Encrypt ECDSA 체인의 기본
  • secp384r1(P-384) — 일부 EV 인증서와 Windows 엔드포인트가 선호
  • secp521r1(P-521) — 외부에서는 드물고, 주로 내부 컴플라이언스 용도

Ed25519 인증서는 공개 web TLS에서는 아직 흔치 않다(Chrome이 109에서야 추가). 하지만 내부 메시 TLS, 코드 서명, SSH에서는 보인다. 디코더는 OID로 라벨을 붙이므로 hex를 외울 필요는 없다.

번들 파일과 도구의 처리 범위

PEM 번들은 단지 -----BEGIN CERTIFICATE----- 블록을 여러 개 이어 붙인 것이다. 서버가 leaf + 중간 + 루트를 체인 파일 하나로 전달하거나, CA가 fullchain.pem을 그대로 배포할 때 나타난다.

ZeroTool 디코더는 입력의 첫 번째 인증서만 디코드하며, 상태 배너로 몇 장이 감지됐는지 알린다. 다른 위치를 보고 싶다면 그 블록만 다시 붙여 넣는다. 인증서 간 서명 검증은 도구 범위 밖이다 — 그 작업은 로컬에서 openssl verify -CAfile chain.pem cert.pem으로 처리한다.

디코드 결과에서 “어?” 싶을 때 점검할 것

운영 리뷰에서 다음 패턴은 빨간 깃발이다:

  • 공개 TLS 리프 인증서의 notAfternotBefore로부터 13개월 이상 떨어져 있다. 2020년 9월부터 Apple, Mozilla, Google이 일제히 공개 TLS 수명 상한을 398일로 통일했다. 그 이상이라면 비공개 CA, 설정이 잘못된 사내 PKI, 또는 상한 도입 전의 오래된 인증서 중 하나다.
  • CN은 채워져 있지만 SAN이 없음. 위에서 언급했듯 브라우저는 거부한다 — 새 배포를 깨뜨릴 만한 레거시 내부 CA 흐름을 발견하는 데 유용하다.
  • 서버 인증서에 CA = true. 잘못 발급되었거나, 리프로 착각된 중간 인증서이거나 둘 중 하나다.
  • AIA에 OCSP URL은 있지만 OCSP Must-Staple 플래그가 없음. 대부분의 배포에서는 문제없지만 stapling 강제 환경이라면 발급 시점에 OCSP Must-Staple 확장(OID 1.3.6.1.5.5.7.1.24)을 추가해야 한다.

openssl x509 -text와의 관계

openssl x509 -in cert.pem -noout -text
openssl x509 -in cert.pem -noout -fingerprint -sha256
openssl x509 -in cert.pem -noout -dates -subject -issuer -ext subjectAltName

openssl은 망라적이고 스크립트화 친화적이다. ZeroTool 디코더는 인시던트 대응 중 “이 인증서가 정확히 뭔가?”라는 단발 질문에 더 빠르다. 두 도구는 보완 관계이지 대체가 아니다. openssl은 파이프라인에, 디코더는 슬랙 스레드에 올라온 PEM을 즉시 읽어야 하는 순간에.

운영자가 신경 쓰는 프라이버시 관점

운영 인증서를 낯선 웹 도구에 붙여 넣는 행위는 소프트 누설이다. 인증서 자체는 공개 정보이며 모든 TLS 클라이언트가 받는 데이터지만, 붙여 넣는 행위 자체가 “지금 이 순간 우리 회사의 누군가가 그 인증서를 디버깅 중”이라는 사실을 상대 서버에 전달한다. 일부 컴플라이언스 프레임워크는 이 패턴을 감사 로그에 표시한다.

ZeroTool 디코더는 브라우저 탭 안에서 완결된다. PEM은 어떤 서버에도 도달하지 않는다 — 초기 페이지 로드 이후 네트워크 요청은 발생하지 않는다. DevTools → Network를 열고 PEM을 붙여 디코드한 뒤, 요청 횟수가 변하지 않는 것을 확인하면 직접 검증할 수 있다.

더 읽을거리