본문 해시가 일치하지 않음
메일링 리스트가 153바이트의 푸터를 추가했습니다. 헤더는 project.example의 키로 여전히 검증되므로 위조가 아닙니다.
해결 본문을 그대로 두는 리스트, 또는 ARC(RFC 8617)를 사용해 이후 수신 서버가 원래 결과를 볼 수 있게 합니다.
body-hash-mismatch테마
수신 메일 서버가 찾는 방식 그대로 도메인의 DKIM 레코드를 조회하거나, 메일을 붙여넣어 서명을 검증합니다: 어떤 셀렉터가 응답하는지, 누가 서명하는지, 각 키가 얼마나 강한지, DMARC와 정렬되는지 확인할 수 있습니다.
DNS는 셀렉터 목록을 보여 줄 수 없으므로, 메일 제공업체가 쓰는 셀렉터 140개를 selector._domainkey.에 도메인을 붙인 이름으로 조회합니다
위 칸에 도메인을 입력하세요: 확인 도구가 메일 제공업체가 쓰는 셀렉터 140개를 조회하고, 발견한 모든 키를 길이와 제공업체와 함께 보여 주며, 아무것도 찾지 못하면 그대로 알려 줍니다. 셀렉터를 알고 있다면 도메인 + 셀렉터를 선택하세요. 그것이 가장 먼저 확인됩니다.
직접 조회하려면 셀렉터가 필요합니다. DNS는 셀렉터 목록을 보여 줄 수 없기 때문입니다: dig +short TXT selector._domainkey.example.com, Windows에서는 같은 이름으로 nslookup -type=TXT를 사용합니다. 셀렉터는 모든 메일의 DKIM-Signature에 있는 s= 값입니다(RFC 6376 §3.6.2.1).
dig는 텍스트만 보여 줄 뿐 그 의미는 알려 주지 않습니다: 키 길이를 해독하지 않고, 제공업체의 CNAME을 따라가지 않으며, 대기용 자리와 고장 난 설정을 구분하지 못합니다. 이 DKIM 레코드 확인 도구가 그 부분을 채워 줍니다.
dig +short TXT selector._domainkey.example.com이 이름의 모든 TXT 문자열을 보여 줍니다: 이어 붙이면 키를 읽을 수 있습니다.
dig가 보여 주는 것과 보여 주지 않는 것
$ dig +short TXT 20251104._domainkey.google.com 1
"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQ…" "…k2A8ZfpQIDAQAB"2
$ dig +short TXT 20251104._domainkey.google.com | tr -d '" \n' | sed 's/.*p=//' | base64 -d | openssl pkey -pubin -inform DER -noout -text | head -1 3
Public-Key: (2048 bit)
$ dig +short TXT selector1._domainkey.microsoft.com 4
selector1-microsoft-com._domainkey.microsoft.onmicrosoft.com.
먼저 한 문장으로 답이 나옵니다. 그 아래에는 누가 서명하는지와 키 자체가 나옵니다: 각 태그를 실제 문자 위치에 색으로 표시하고, 키 길이를 RFC 8301 기준으로 보여 줍니다. 남색 배경에는 조회한 모든 셀렉터가 표시되어 무엇을 시도했는지 볼 수 있습니다.
점수는 없습니다: 폐기된 키나 제공업체의 빈 대기용 셀렉터는 정상이며, “일반 셀렉터에서 찾지 못함”은 “DKIM 없음”을 뜻하지 않습니다.

판정한 문장으로 키가 몇 개인지, 무엇을 해야 하는지 알려 줍니다.서명자제공업체마다 카드 하나. CNAME 또는 문서화된 셀렉터로 판별합니다. 각 셀렉터 칩을 누르면 해당 키가 열립니다.키각 태그를 문자 위치에 색으로 표시하고, 키 길이를 RFC 8301 기준으로 보여 줍니다.셀렉터 탐색조회한 이름이 응답할 때마다 불이 켜집니다: 초록은 키, 노랑은 1024비트, 점선은 대기용 자리입니다.DMARC 정렬이 도메인의 d=를 가진 키가 DMARC 정책과 정렬되는지 보여 줍니다.DKIM(DomainKeys Identified Mail)은 발신 서버가 각 메시지에 붙이는 서명이며, 수신 서버는 도메인이 DNS에 게시한 공개 키로 이를 확인합니다(RFC 6376). 유효한 서명은 두 가지를 증명합니다: d=의 도메인이 서명했다는 것, 그리고 서명된 부분이 그 뒤로 바뀌지 않았다는 것입니다.
발신자는 본문을 해시해 bh=에 넣고, 선택한 헤더와 그 해시를 개인 키로 서명해 b=에 넣습니다. 수신자는 d=와 s=를 읽고 s._domainkey.d에서 키를 가져와 두 과정을 다시 수행합니다. 본문이나 서명된 헤더가 한 바이트라도 바뀌면 실패합니다.
DKIM은 실패했을 때 어떻게 할지는 말하지 않으며, 어떤 도메인이든 서명할 수 있습니다. 그것은 DMARC의 역할입니다: d=가 From: 도메인과 일치하는 유효한 서명을 요구합니다.
메시지 하나, 발신 측에서 서명하고 수신 측에서 확인
DKIM-Signature: v=1; a=rsa-sha256; d=example.com; s=dnsrobot-sample; h=from:from:to:subject:…; bh=fohb/a+auh1W9cUN…; b=fF0aMr3Uo+mbgG…
dnsrobot-sample._domainkey.example.com TXT?"v=DKIM1; k=rsa; p=MIIBIjANBgkq…"RSA 2048
전송된 그대로의 메시지
dkim=pass header.d=example.com
전달 중에 Subject가 수정된 같은 메시지
dkim=fail (signature-mismatch)
셀렉터는 DKIM 키 하나의 이름입니다: 서명의 s=이자, 키가 있는 DNS 이름의 첫 번째 레이블입니다(s1._domainkey.github.com). 도메인은 원하는 만큼 둘 수 있습니다: 제공업체마다, 서버마다, 해마다 하나씩(RFC 6376 §3.1).
내 셀렉터를 찾으려면 도메인이 보낸 메일을 열어 원본을 보세요: Gmail의 더보기(⋮) › 원본 보기에 DKIM-Signature가 나오며, 그 s=가 셀렉터입니다. 또는 위의 메일 검증에 메일을 붙여넣으세요.
대부분의 제공업체는 고정된 이름을 쓰므로 탐색으로 찾을 수 있습니다. Amazon SES, Postmark, SparkPost, HubSpot은 계정마다 이름을 만들기 때문에 메일이나 제공업체의 대시보드에서만 알 수 있습니다.
서명에서 키까지, 그리고 제공업체가 쓰는 이름
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=github.com; s=s1; …
s1._domainkey.github.comCNAME s1.domainkey.u51742174.wl175.sendgrid.netRSA 2048
| Google Workspace | 관리자가 접두사를 바꿀 수 있음 | |
|---|---|---|
| Microsoft 365 | selector1 · selector2 | 테넌트로 연결되는 CNAME. 하나는 사용 중, 하나는 대기 |
| Twilio SendGrid | s1 · s2 | u<id>.wl<n>.sendgrid.net으로 가는 CNAME |
| Mailchimp | k2 · k3 | dkim2/dkim3.mcsv.net으로 가는 CNAME |
| Fastmail | fm1 · fm2 · fm3 | CNAME 세 개, 한 번에 하나만 채움 |
| Proton Mail | protonmail · 2 · 3 | domains.proton.ch로 가는 CNAME |
| Amazon SES | 무작위 토큰 3개 | 각각 무작위 32자: 추측할 수 없음 |
| Postmark | 20240101000000pm | 날짜와 “pm”: 추측할 수 없음 |
| SparkPost | scph + MMYY | scph와 키를 만든 달 |
| HubSpot | hs1-<HubID> | 내 계정 번호 |
DKIM 레코드는 세미콜론으로 구분한 태그=값 쌍으로 이루어진 TXT 레코드이며, 필수인 것은 공개 키인 p=뿐입니다. v=DKIM1은 생략할 수 있지만, 있으면 맨 앞에 와야 합니다(RFC 6376 §3.6.1).
k=는 키 종류(ed25519라고 쓰지 않으면 rsa), t=는 플래그(y는 테스트, s는 엄격), h=는 해시 제한, s=는 서비스 제한입니다. 빈 p=는 키를 의도적으로 폐기합니다.
태그 이름은 대소문자를 구분하며, 같은 태그를 반복하면 레코드가 무효가 됩니다. 알 수 없는 태그는 무시됩니다.
태그별로 본 레코드
v=DKIM1; k=rsa; t=s; h=sha256; p=MIIBIjANBgkqhkiG9w…IDAQAB
| 태그 | 설정 내용 | 값 | 기본값 | 참고 |
|---|---|---|---|---|
| v= | 버전 | DKIM1 | DKIM1 | 권장, 있으면 맨 앞 |
| k= | 키 종류 | rsa · ed25519 | rsa | 알 수 없는 종류: 키 무시 |
| p= | 공개 키(base64) | 키 또는 비어 있음 | 필수 | 비어 있음 = 폐기 |
| t= | 플래그 | y 테스트 · s 엄격 | 없음 | y: 서명 없는 것으로 취급 |
| h= | 허용하는 해시 | sha256(sha1은 금지) | 전체 | sha1만 있으면 실패(RFC 8301) |
| s= | 서비스 종류 | * | 다른 서비스 전용: 무시됨 | |
| n= | 사람을 위한 메모 | 아무 텍스트 | – | 소프트웨어는 읽지 않음 |
p=의 길이와 필요한 TXT 문자열 수
255바이트 단위의 TXT 문자열 경계(“v=DKIM1; k=rsa; p=”를 포함해 계산)
2048비트 RSA를 사용하세요. RFC 8301은 검증 측이 1024비트 키를 여전히 받아들이도록 요구하지만, 서명자에게는 2048비트 이상을 요구하고 검증 측은 1024비트 미만의 키를 거부하도록 정합니다. M3AAWG는 1024비트를 “공격당할 수 있는 한계”라고 부르며, 512비트 키가 약 72시간, 약 75달러로 해독되었다고 보고합니다.
Ed25519 키는 작고(p=는 44자), 하위 호환을 위해 RFC 8463은 서명자가 그 옆에 RSA 서명을 추가하도록 허용합니다: 셀렉터 둘, 서명 둘입니다(RFC 8463).
교체는 새 셀렉터에 새 키를 게시하고, 서명을 전환하고, 전송 중인 메일을 위해 이전 키를 7~30일 두었다가 그 p=를 비우는 순서로 합니다. M3AAWG는 6개월마다를 권합니다. Fastmail이나 Microsoft 365 같은 제공업체는 대기용 셀렉터로 이를 대신해 줍니다.
실제 키가 RFC 8301 기준의 어디에 있는지
끊김 없는 교체(M3AAWG)
DKIM은 서명된 바이트가 바뀌었을 때, 키를 찾을 수 없을 때, 키가 서명과 맞지 않을 때 실패합니다. 메시지를 바꾸지 않는 전달은 DKIM을 그대로 유지합니다(SPF와 다릅니다). 메일링 리스트는 푸터를 붙이거나 제목에 태그를 달기 때문에 대개 DKIM을 깹니다.
이 페이지의 검증기는 본문 해시와 헤더 서명을 따로 확인하므로, 전달 중에 붙은 푸터와 위조를 구분할 수 있습니다.
메일링 리스트가 153바이트의 푸터를 추가했습니다. 헤더는 project.example의 키로 여전히 검증되므로 위조가 아닙니다.
해결 본문을 그대로 두는 리스트, 또는 ARC(RFC 8617)를 사용해 이후 수신 서버가 원래 결과를 볼 수 있게 합니다.
body-hash-mismatch서명한 뒤 서명된 헤더가 바뀌었거나(리스트가 Subject에 자기 이름을 붙임), 셀렉터에 지금 다른 키가 들어 있습니다.
해결 서명된 헤더를 고쳐 쓰지 마세요. 키를 교체하지 말고 새 셀렉터에 새 키를 게시하세요.
signature-mismatchs=2023old._domainkey.shop.example이 NXDOMAIN입니다: 그 셀렉터로 서명한 메일이 아직 전송 중인데 셀렉터가 삭제되었습니다.
해결 전환한 뒤에도 이전 키를 7~30일 게시해 두고, 삭제하지 말고 p=를 비우세요.
key-not-found전달: 메시지를 바꾸지 않고 넘겨주는 전달 서비스는 DKIM을 유효하게 유지하므로, SPF가 실패해도 DMARC는 대개 DKIM 덕분에 전달을 견딥니다.
SPF는 어떤 서버가 도메인을 대신해 보낼 수 있는지 알려 주고, DKIM은 도메인이 서명했으며 바뀌지 않았음을 증명하고, DMARC는 둘을 From: 주소에 묶어 실패했을 때 어떻게 할지 알려 줍니다.
각각 다른 질문에 답하므로 셋 다 필요합니다: Gmail과 Yahoo는 대량 발신자에게 SPF와 DKIM, 그리고 DMARC 레코드를 요구하며, RFC 9989는 p=reject인 도메인에 SPF에 기대지 말고 DKIM으로 서명하도록 요구합니다.
레코드 셋, 질문 셋
| 레코드 셋, 질문 셋 | SPF | DKIM | DMARC |
|---|---|---|---|
| 무엇을 증명하나 | 이 서버가 MAIL FROM 도메인을 대신해 보낼 수 있음 | d= 도메인이 서명했고, 서명된 부분은 바뀌지 않음 | From:이 SPF 또는 DKIM을 통과한 도메인과 일치함 |
| 어디에 있나 | github.com의 TXT | s1._domainkey.github.com의 TXT | _dmarc.github.com의 TXT |
| 오늘의 github.com | v=spf1 … ~all | 키 12개, 제공업체 6곳 | p=quarantine; sp=reject |
| 전달을 견디나 | 아니요: 전달 서버의 IP가 등록되어 있지 않음 | 예, 메시지가 바뀌지 않고 도착하면 | 예, 정렬된 DKIM 통과를 통해 |
| 깨지는 때 | 새 발신 서비스가 레코드에 없을 때 | 본문이나 서명된 헤더가 바뀔 때 | From: 도메인에 대해 둘 다 통과하지 못할 때 |
원본 메시지를 열어 DKIM-Signature 헤더를 찾으세요: d=는 서명자, s=는 어떤 키인지, h=는 서명된 헤더 목록이고, bh=와 b=는 수신자가 확인하는 두 해시입니다.
수신자의 판정은 도착할 때 추가되는 별도의 헤더에 있습니다: Authentication-Results: … dkim=pass header.d=example.com header.s=dnsrobot-sample. 영어로 표시되는 Gmail의 Show original 페이지는 이를 “DKIM: 'PASS' with domain example.com”으로 요약합니다.
메시지 하나에 서명자마다 하나씩 여러 서명이 붙을 수 있습니다(내 도메인과 메일 서비스, 또는 메일링 리스트). DMARC에는 d=가 정렬된 유효한 서명이 하나만 있으면 충분합니다.
샘플 메일의 서명을 태그별로
v=11; a=rsa-sha2562; c=relaxed/relaxed3; d=example.com4; s=dnsrobot-sample5; t=17928331206; h=from:from:to:subject:date:message-id:mim…7; bh=fohb/a+auh1W9c…8; b=fF0aMr3Uo+mbgG…9
Authentication-Results: mx.example.net; dkim=pass header.d=example.com header.s=dnsrobot-sample