DNSSEC Part 2 : 용어정리 - RRset, DNSKEY, RRSIG, DS, 트러스트 앵커
today keys : DNSSEC, RRset, DNSKEY, KSK, ZSK, RRSIG, DS, 트러스트 앵커, DO 비트
DNSSEC은 기존 DNS에 네 종류의 레코드를 추가하는 방식으로 동작합니다. 공개키를 담는 DNSKEY, 전자서명값인 RRSIG, 부모 존이 자식 키를 보증하는 DS, 그리고 이 모든 서명의 출발점이 되는 트러스트 앵커입니다.
이번 편에서는 앞으로 계속 반복해서 등장할 이 용어들을 한자리에 정리하겠습니다. 서명이 붙는 단위인 RRset부터 시작해 각 레코드가 무엇을 담고 어디에 위치하는지 확인하고, 마지막에 cloudflare.com 의 실제 값으로 대조해보겠습니다.
1. 서명은 왜 요청해야만 보이는가 (DO 비트)
지난 편에서 +dnssec 옵션 유무에 따라 응답 구성이 달라지는 것을 확인했습니다. 이 차이부터 짚고 넘어가겠습니다.
DNSSEC이 적용된 존은 서명을 항상 가지고 있습니다. 다만 질의하는 쪽이 요청하지 않으면 응답에 실어 보내지 않을 뿐입니다.
이 요청 신호가 EDNS0 헤더에 들어가는 DO(DNSSEC OK) 비트입니다.
dig cloudflare.com A → DO=0 → A 레코드만 응답
dig cloudflare.com A +dnssec → DO=1 → A 레코드 + 서명 관련 레코드 함께 응답
이런 구조인 이유는 응답 크기 때문입니다.
서명 관련 레코드는 하나가 수백 바이트에 달하므로, 검증할 생각이 없는 클라이언트에게까지 보내면 트래픽만 늘어납니다.
어떤 레코드가 추가되는가?
DO=1로 요청했을 때 따라오는 것은 RRSIG만이 아닙니다. 응답 성격에 따라 달라집니다.
| 응답 상황 | 추가되는 레코드 |
| 정상 응답 | RRSIG |
| 하위 존으로 위임 | DS + RRSIG (DS가 없으면 NSEC3 + RRSIG) |
| 존재하지 않는 이름 | NSEC / NSEC3 + RRSIG |
이 레코드들이 각각 무엇을 담고 있는지는 이번 편에서 차례로 정리하겠습니다.
DO=1이 곧 검증을 뜻하지는 않는다
한 가지 구분해둘 것이 있습니다.
DO 비트는 "서명 데이터를 받겠다"는 요청 신호일 뿐, 받은 서명을 실제로 검증하는지는 별개입니다.
실제 환경에서 DO 비트를 사용하는 것은 재귀 리졸버입니다.
클라이언트는 보통 DO=0으로 묻고, 리졸버가 DO=1로 권한 서버들에게 질의해 서명을 수집합니다.
다만 최근의 리졸버 구현은 검증 기능을 꺼두더라도 DO=1로 질의하는 경우가 많습니다.
클라이언트가 서명을 요청해올 때 전달해줄 수 있어야 하기 때문입니다.
따라서 DO 비트만으로는 그 리졸버가 검증을 수행하는지 알 수 없습니다.
검증 여부를 판별하는 지표는 따로 있으며, 이 부분은 다음 편에서 다루겠습니다.
서명이 없는 존에 DO=1로 질의하면
여기서 한 가지 궁금해지는 부분이 있습니다. DNSSEC이 적용되지 않은 존에 +dnssec을 붙여 질의하면 어떻게 될까요.
결론부터 말하면 정상 응답이 돌아옵니다.
DO 비트는 "서명이 있으면 함께 보내달라"는 요청이지 "서명을 반드시 내놓으라"는 요구가 아니기 때문입니다.
서명이 없는 존은 평소와 동일하게 응답하고, 단지 서명 관련 레코드가 붙지 않을 뿐입니다.
zigi@ZIGI:~$ dig zigispace.net A +dnssec
; <<>> DiG 9.18.39-0ubuntu0.24.04.5-Ubuntu <<>> zigispace.net A +dnssec
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 64035
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
;; QUESTION SECTION:
;zigispace.net. IN A
;; ANSWER SECTION:
zigispace.net. 10 IN A 27.0.236.142
에러도 아니고 거부도 아닙니다. status는 정상을 뜻하는 NOERROR이고,
OPT 섹션의 flags: do는 서버가 DO 비트를 인식했다는 표시입니다.
다만 ANSWER 섹션에는 서명 관련 레코드 없이 A 레코드만 돌아옵니다.
다만 검증을 수행하는 리졸버 입장에서는 이 상황이 단순히 "서명이 없으니 그냥 통과"로 끝나지 않습니다.
서명이 없다는 사실 자체를 별도로 증명받는 절차를 거치는데, 이 부분은 4편에서 부재 증명과 함께 다루겠습니다.
2. RRset - 서명이 붙는 최소 단위
DNSSEC에서 서명은 레코드 한 줄마다 붙는 것이 아닙니다. RRset(Resource Record set) 이라는 묶음 단위로 붙습니다.
RRset은 이름(Owner Name)과 타입(Type)이 모두 같은 레코드들의 집합입니다. 예를 들어 아래와 같은 존이 있다고 하면,
example.com. A 10.10.10.10
example.com. A 10.10.10.11
www.example.com. A 10.10.10.20
www.example.com. AAAA 2001:db8::20
이 네 줄은 세 개의 RRset으로 나뉩니다.
| RRset | 포함 레코드 | 비고 |
example.com A |
2줄 (.10, .11) |
이름·타입이 같아 한 묶음 |
www.example.com A |
1줄 | 이름이 달라 별개 |
www.example.com AAAA |
1줄 | 이름은 같지만 타입이 달라 별개 |
첫 번째 RRset은 레코드가 두 줄이지만 서명은 하나만 붙습니다.
반대로 www.example.com은 이름이 같아도 A와 AAAA가 서로 다른 RRset이므로 서명이 각각 하나씩, 총 두 개 붙습니다.
이 단위 개념이 중요한 이유는 검증할 때 그대로 적용되기 때문입니다.
리졸버는 레코드를 한 줄씩 확인하는 것이 아니라 RRset 전체를 묶어서 검증합니다.
따라서 공격자가 두 줄 중 한 줄만 조작해도 그 RRset 전체의 서명이 무효가 됩니다.
3. DNSKEY - 공개키를 담는 레코드
DNSSEC은 공개키 암호 방식을 사용합니다. 권한 서버가 개인키로 서명하고, 리졸버가 공개키로 검증하는 구조입니다.
여기서 리졸버가 사용할 공개키를 배포하는 통로가 필요한데,
DNSSEC은 이것을 별도 채널이 아니라 DNS 레코드 자체로 해결합니다. 그 레코드가 DNSKEY입니다.
cloudflare.com. IN DNSKEY 257 3 13 mdsswUyr3DPW132mOi8V9xESWE8j...
cloudflare.com. IN DNSKEY 256 3 13 koPbw9wmYZ7ggcjnQ6ayHyhHaDNM...
필드 구성은 다음과 같습니다.
| 필드 | 예시 값 | 의미 |
| Flags | 257 / 256 |
키의 역할 구분 (아래 설명) |
| Protocol | 3 |
고정값 (DNSSEC용) |
| Algorithm | 13 |
알고리즘 번호 (13=ECDSA P-256, 8=RSA/SHA-256) |
| Public Key | mdsswUyr... |
Base64로 인코딩된 공개키 |
여기 담기는 것은 공개키뿐입니다.
개인키는 DNS 응답에 절대 실리지 않고 권한 서버 내부(또는 HSM)에만 보관됩니다.
HTTPS에서 서버 인증서에 공개키만 들어 있고 개인키는 서버에만 있는 것과 동일한 구조입니다.
4. KSK와 ZSK - 키를 두 개로 나눈 이유
앞의 출력에서 DNSKEY가 두 줄이었던 것을 보셨을 텐데, 이는 존 하나가 역할이 다른 키 두 개를 운용하기 때문입니다.
이 두 키는 Flags 값으로 구분됩니다.
| 구분 | KSK (Key Signing Key) | ZSK (Zone Signing Key) |
| Flags | 257 | 256 |
| 용도 | DNSKEY(KSK/ZSK) RRset을서명하는 키 | 존 안의 실제 레코드(A, MX, TXT 등)에 서명하는 키 |
| 사용 빈도 및 교체 주기 |
레코드가 바뀔 때마다 사용되어 노출 위험 큼 자주 교체합니다(보통 1~3개월 주기). |
사용 빈도가 낮고, 교체 주기가 김(보통 1년 이상) |
여기서 한 가지 짚고 넘어갈 점이 있습니다. KSK와 ZSK는 각각 공개키와 개인키로 이루어진 한 쌍(key pair)입니다.
"KSK가 공개키인가 개인키인가"라는 질문은 성립하지 않습니다.
서명할 때는 개인키를 쓰고, DNSKEY 레코드로 게시되는 것은 공개키입니다.
왜 굳이 나눴는가
키를 하나만 쓴다면 더 단순했을 텐데 둘로 나눈 이유는 운영 편의성 때문입니다.
뒤에서 설명할 DS 레코드는 KSK의 해시값을 부모 존에 등록해둔 것입니다.
그런데 이 등록은 레지스트라를 통해 진행해야 하는 번거로운 절차입니다.
만약 키가 하나뿐이었다면 키를 교체할 때마다 매번 부모 존 등록을 갱신해야 합니다.
키를 둘로 나누면 이 문제가 해결됩니다.
자주 바꿔야 하는 키(ZSK)는 외부 절차 없이 교체하고, 외부 절차가 필요한 키(KSK)는 거의 안 바꾸도록 역할을 분리한 것입니다.
5. RRSIG - 전자서명을 담는 레코드
RRSIG는 특정 RRset에 대한 전자서명값을 담는 레코드입니다.
앞서 확인한 서명 대상 단위(RRset) 하나당 RRSIG 하나가 대응됩니다.
cloudflare.com. 266 IN RRSIG A 13 2 300 20261005000000 20260915000000 34505 cloudflare.com. DYYZ/bhHSAIlpvu...
한 줄로 보면 복잡해 보이지만 구성은 단순합니다.
| 필드 | 예시 값 | 의미 |
| Type Covered | A |
어떤 타입의 RRset을 서명했는가 |
| Algorithm | 13 |
서명 알고리즘 |
| Labels | 2 |
소유자 이름의 레이블 수 |
| Original TTL | 300 |
서명 대상 RRset의 원래 TTL |
| Expiration | 20261005000000 |
서명 만료 시각 |
| Inception | 20260915000000 |
서명 유효 시작 시각 |
| Key Tag | 34505 |
어떤 키로 서명했는지 식별하는 번호 |
| Signer's Name | cloudflare.com. |
서명한 존의 이름 |
| Signature | DYYZ/bhHSAI... |
실제 서명값 |
마지막 Signature 필드가 실제 암호 연산의 결과물이고, 앞의 필드들은 리졸버가 검증할 때 필요한 부가 정보입니다.
서명값은 어떻게 만들어지는가
그렇다면 이 Signature 값은 어떤 과정을 거쳐 나온 것일까요.

먼저 RRSIG 메타데이터 필드를 만들고, 서명 할 RRset를 연결합니다.
이 두 가지 데이터가 서명 대상이 되는 데이터가 되고, 이렇게 연결하는 과정을 직렬화라고 합니다.
서명 대상 데이터 직렬화를 하고 나면, 이를 해시 계산하여 해시 계산 값을 만듭니다.

만들어진 해시 계산 값은 개인 키로 서명하여 서명 값을 만드는 데,
일반 레코드는 ZSK 개인키를 사용하고, DNSKEY의 경우에만 KSK 개인키를 사용합니다.
앞서 살펴본 두 키의 역할 구분이 여기서 적용되는 것입니다.

만들어진 서명 값은 RRSIG 메타데이터와 합쳐져서, RRSIG 레코드의 값으로 사용됩니다.
여기서 짚어둘 점이 있습니다.
서명 대상에 RRset뿐 아니라 RRSIG의 메타데이터 필드도 함께 포함된다는 것입니다.
만약 RRset만 서명했다면 어떻게 될까요?
공격자가 Expiration 값만 미래로 바꿔치기해도 서명값은 여전히 유효하므로 검증이 통과해버립니다.
만료된 서명을 영구히 재사용하는 공격이 가능해지는 셈입니다. 유효기간과 키 정보까지 서명 대상에 넣는 이유가 여기 있습니다.
또 하나는 서명에 개인키가 쓰인다는 점입니다.
앞서 본 대로 DNSKEY 레코드로 공개되는 것은 공개키이고, 개인키는 권한 서버 내부에만 보관됩니다.
따라서 그 개인키를 가진 주체만 유효한 서명값을 만들어낼 수 있습니다.
공격자가 아무리 그럴듯한 레코드를 위조해도 서명값까지 맞춰낼 수는 없습니다.
리졸버는 이 과정을 거꾸로 밟아 검증합니다.
받은 데이터로 직렬화와 해시 계산을 똑같이 수행해 값을 구하고, RRSIG의 서명값을 공개키로 복호화해 나온 값과 비교하는 방식입니다.
실제 검증 절차는 다음 편에서, 직렬화 규칙이나 알고리즘 같은 내부 동작은 시리즈 후반의 심화 편에서 자세히 다루겠습니다.
여기서 주목할 것은 Expiration과 Inception입니다. DNSSEC 서명에는 유효기간이 있습니다.
서명 자체가 정상이어도 이 기간을 벗어나면 검증에 실패합니다.
이 때문에 존 관리자는 만료 전에 주기적으로 재서명해야 하고, 리졸버 쪽에서는 시스템 시간이 정확해야 합니다.
NTP 동기화가 어긋난 서버에서 정상 도메인이 전부 조회되지 않는 장애가 이 지점에서 발생합니다.
Key Tag도 짚어둘 필요가 있습니다.
존에 DNSKEY가 여러 개(KSK, ZSK, 교체 중인 구키 등) 있을 때, 리졸버에게 "이 중 몇 번 키로 검증하라"고 알려주는 식별 번호입니다.
6. DS - 부모가 자식의 키를 보증하는 레코드
지금까지의 내용만으로는 아직 빈틈이 있습니다.
리졸버가 cloudflare.com의 DNSKEY를 받아왔다고 해도, 그 DNSKEY 자체가 진짜라는 보장이 없습니다.
공격자가 자기 키로 바꿔치기하고 자기 개인키로 서명을 붙이면 겉보기에는 완벽하게 맞아떨어집니다.
이 문제를 해결하는 것이 DS(Delegation Signer) 레코드입니다.
cloudflare.com. IN DS 2371 13 2 32996839A6D808AFE3EB4A795A0E6A7A39A76FC52FF228B22B76F6D63826F2B9
| 필드 | 예시 값 | 의미 |
| Key Tag | 2371 |
어떤 KSK에 대한 것인지 |
| Algorithm | 13 |
해당 키의 알고리즘 |
| Digest Type | 2 |
해시 알고리즘 (2=SHA-256) |
| Digest | 32996839A6D8... |
KSK 공개키의 해시값 |
DS의 핵심은 마지막 Digest 필드입니다.
자식 존 KSK 공개키를 해시한 값이 담깁니다. 여기서 해시 대상은 KSK 공개키이며, ZSK는 DS와 아무 관련이 없습니다.
그리고 가장 중요한 특성이 있습니다. DS 레코드는 자기 존이 아니라 부모 존에 위치합니다.
cloudflare.com의 DS는 cloudflare.com존이 아니라 com 존에 들어 있습니다.
그리고 com 존이 이 DS 레코드를 자신의 ZSK 개인키로 서명하며, 그 결과로 만들어지는 것이 RRSIG DS 레코드입니다.
cloudflare.com. 86400 IN DS 2371 13 2 32996839A6D8...
cloudflare.com. 86400 IN RRSIG DS 13 2 86400 ... <com ZSK 키태그> com. <서명값>
두 번째 줄을 보면 소유자 이름은 cloudflare.com.인데 서명자(Signer's Name)는 com.입니다.
"부모가 자식의 데이터를 서명해줬다"는 사실이 이 두 필드의 차이로 드러납니다.
즉 이런 관계가 성립합니다.
- com 존이 cloudflare.com의 DS를 보유
- 리졸버가 cloudflare.com의 KSK 공개키를 받아 해시 계산
- 그 해시값이 com에 있던 DS의 Digest와 일치하는지 대조
- 일치하면 "이 KSK는 부모가 등록해둔 진짜 키"로 확정
부모가 자식의 키를 보증하는 이 구조는 HTTPS 인증서 체인에서 상위 CA가 하위 CA 인증서에 서명해주는 것과 동일한 발상입니다.
다만 위 흐름에는 한 가지 전제가 생략되어 있습니다.
대조 기준이 되는 DS 값 자체도 먼저 검증을 거쳐야 합니다.
공격자가 DS까지 함께 위조했다면 대조가 성립해도 의미가 없기 때문입니다.
그래서 실제 리졸버는 앞서 본 RRSIG DS를 com의 ZSK로 검증해 DS가 진짜임을 확인한 뒤에야 해시 대조로 넘어갑니다.
이 검증 순서는 다음 편(체인 오브 트러스트)에서 자세히 다루겠습니다.
7. 트러스트 앵커 - 루트만의 예외 처리
DS 구조를 따라 올라가면 한 가지 문제에 도달합니다.
- cloudflare.com의 키는 → com이 보증 (DS)
- com의 키는 → 루트가 보증 (DS)
- 루트의 키는 → 보증해줄 부모가 없음
루트 존에는 부모가 존재하지 않으므로 DS를 등록할 곳도 없습니다. 신뢰 사슬이 여기서 끊어집니다.
DNSSEC은 이 지점을 트러스트 앵커(Trust Anchor) 라는 예외 장치로 처리합니다.
루트 KSK에 대한 정보를 DNS가 아닌 별도의 안전한 경로로 미리 받아서 리졸버 설정에 직접 저장해두는 방식입니다.
IANA가 이 값을 공식 배포하고, 각 리졸버 운영자가 이를 가져와 설정에 반영합니다.
여기서 배포되는 값은 공개키 자체가 아니라 공개키를 해시한 값으로, 앞서 살펴본 DS 레코드와 동일한 형식입니다.
루트만 이 값을 등록할 부모 존이 없어 리졸버에 직접 저장할 뿐, 값의 형태와 대조 방식은 다른 계층의 DS와 같습니다.
이렇게 저장된 값은 리졸버가 검증 없이 신뢰하는 유일한 값이 됩니다.
나머지 모든 키는 이 앵커에서 출발해 DS를 타고 내려오며 검증되므로, 앵커 하나가 전체 신뢰 사슬의 출발점 역할을 합니다.
OS나 브라우저에 루트 CA 인증서가 미리 내장되어 있는 것과 정확히 같은 개념입니다.
최상위 신뢰만은 암호학적으로 증명하는 것이 아니라 사전에 안전하게 배포된 값을 믿는 것으로 처리합니다.
8. 용어 한눈에 정리
| 용어 | 정체 | 위치 | 핵심 역할 |
| RRset | 레코드 묶음 | - | 이름+타입이 같은 레코드 집합, 서명의 최소 단위 |
| DNSKEY | 레코드 타입 | 자기 존 | KSK·ZSK의 공개키를 게시 (257=KSK, 256=ZSK) |
| KSK | 키 쌍 | 자기 존 | DNSKEY RRset에만 서명, 해시가 DS로 등록됨 |
| ZSK | 키 쌍 | 자기 존 | 존 안의 실제 레코드에 서명, 자주 교체 |
| RRSIG | 레코드 타입 | 자기 존 | RRset 하나에 대한 서명값 + 유효기간 |
| DS | 레코드 타입 | 부모 존 | 자식 KSK 공개키의 해시값 |
| 트러스트 앵커 | 리졸버 설정값 | 리졸버 내부 | 루트 KSK 공개키의 해시, 신뢰 사슬의 시작점 |
📌 한 줄 요약: KSK가 ZSK를 서명하고, ZSK가 실제 레코드를 서명하며, KSK의 해시(DS)를 부모 존이 보증합니다.
그리고 부모가 없는 루트만 트러스트 앵커로 예외 처리됩니다.
9. 직접 확인해보기
지금까지 설명한 레코드들을 실제로 조회해보겠습니다.
DNSKEY 조회 - KSK와 ZSK 구분하기
zigi@ZIGI:~$ dig cloudflare.com DNSKEY +short
257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo0dxCjjnopKl+GqJxpVXckHAeF+ KkxLbxILfDLUT0rAK9iUzy1L53eKGQ==
256 3 13 oJMRESz5E4gYzS/q6XDrvU1qMPYIjCWzJaOau8XNEZeqCYKD5ar0IRd8 KqXXFJkqmVfRvMGPmM1x8fGAa2XhSA==
두 줄이 출력되는데, 앞의 숫자가 257인 줄이 KSK, 256인 줄이 ZSK입니다. 세 번째 숫자(13)는 알고리즘 번호로, ECDSA P-256을 의미합니다.
DS 조회 - 부모 존에 등록된 해시값
zigi@ZIGI:~$ dig cloudflare.com DS +short
2371 13 2 32996839A6D808AFE3EB4A795A0E6A7A39A76FC52FF228B22B76F6D6 3826F2B9
출력된 마지막 긴 16진수 문자열이 KSK 공개키의 SHA-256 해시입니다. 이 값은 cloudflare.com이 아니라 com 존에서 응답한 것입니다. 아래 명령으로 어느 서버가 이 값을 돌려주는지 직접 확인할 수 있습니다.
dig cloudflare.com DS +trace
출력 중간에 gtld-servers.net 계열 서버(=com TLD 서버)가 DS 레코드를 응답하는 구간이 보입니다.
RRSIG 조회 - 유효기간 확인하기
dig cloudflare.com A +dnssec +multi
+multi 옵션을 붙이면 RRSIG 필드가 줄바꿈되어 출력되므로 읽기 편합니다. Expiration과 Inception 값을 보면 이 서명이 언제까지 유효한지 확인할 수 있습니다.
10. 참고 자료 (Reference Links)
- RFC 4034 - Resource Records for the DNS Security Extensions
- RFC 4035 - Protocol Modifications for the DNS Security Extensions
- IANA - DNSSEC Algorithm Numbers
- IANA - Root Zone Trust Anchors
※ 이전 포스팅
[DNSSEC ① - DNSSEC은 왜 필요한가]
다음 편에서는 이번에 정리한 레코드들이 어떻게 하나의 사슬로 연결되는지 다루겠습니다. 트러스트 앵커에서 출발해 루트, TLD를 거쳐 최종 도메인까지 신뢰가 전달되는 과정, 즉 체인 오브 트러스트(Chain of Trust)입니다. 이번 편에서 미뤄둔 'DS 자체를 먼저 검증해야 하는 순서'도 여기서 함께 정리하겠습니다.