DKIM Alignment: 다들 헷갈리는 바로 그 부분

DKIM 서명이 유효하다고 DMARC를 통과하는 게 아니다. 서명은 '어떤 도메인이 서명했다'만 증명한다. 그 도메인이 당신의 From 주소를 대변할 자격이 있는지는 alignment가 결정한다.

완벽하게 유효한 DKIM 서명을 가지고도 DKIM에 실패할 수 있다.

Authentication-Results 헤더에 dkim=pass가 찍혀 있는데 같은 메일의 DMARC 리포트에는 dkim=fail이 떠 있는 걸 보고 어리둥절해하는 사람을 여러 번 봤다. 서명은 검증됐다. 수학적으로 맞았다. body hash도 일치했다. 그런데 DMARC가 그 메일을 버렸다.

이 간극을 설명하는 단어는 “DKIM” 안에 없다. alignment다. 그리고 이메일 인증에서 가장 오해받는 개념이기도 하다.

DKIM이 실제로 증명하는 것

DKIM(RFC 6376)은 좁은 일을 하나 잘한다. 보내는 시스템이 본문과 지정한 헤더 몇 개를 해시하고, 그 해시를 개인키로 서명한 뒤 DKIM-Signature 헤더를 메일에 붙인다. 받는 쪽은 DNS — selector._domainkey.signingdomain.com — 에서 공개키를 꺼내 서명을 검증한다.

검증이 되면 딱 하나의 사실을 알게 된다. d= 태그에 적힌 도메인이 이 메일에 책임을 졌다는 것. RFC 6376은 이걸 SDID(Signing Domain Identifier)라고 부른다. 그게 전부다. 주장은 거기서 끝난다.

빠진 게 뭔지 보자. 서명은 사람이 읽는 From: 주소에 대해 아무 말도 하지 않는다. 전혀. d=는 DNS에 키를 가진 어떤 도메인이든 될 수 있다. ceo@yourcompany.com에서 온 메일이 d=sendgrid.net의 흠 하나 없는 서명을 달고 와도 DKIM은 만족한다. 서명은 유효하다. SendGrid가 실제로 서명한 게 맞다. 전부 사실인데, 정렬된 건 하나도 없다.

DMARC가 그 위에 alignment를 얹는다

DMARC(RFC 7489)는 DKIM이 끝내 묻지 않는 질문을 던지는 계층이다. 이 메일을 서명한 도메인이 From: 헤더의 도메인과 무슨 관계라도 있는가?

3.1.1 절이 못 박는다. DKIM이 DMARC에 카운트되려면 d= 도메인이 RFC5322.From 도메인과 *정렬(align)*돼야 한다. relaxed 모드에서는 두 도메인의 Organizational Domain이 일치해야 하고, strict 모드에서는 전체 도메인이 정확히 일치해야 한다.

그래서 실패하는 길이 두 갈래다. 서명 자체가 깨질 수 있다 — 잘못된 키, 변형된 본문, 만료. 이건 검증(verification) 실패다. 아니면 서명은 멀쩡한데 d=가 그냥 남의 것일 수 있다. 이건 정렬(alignment) 실패다. DMARC 결과는 똑같이 나오는데 둘은 아무 상관이 없다. “DKIM 실패했어요”라는 문장이 그 자체로 쓸모없는 이유가 이거다. 어느 쪽으로 실패했는데?

다들 여기서 틀린다. 사람들은 DKIM이 검증되는지를 점검한다. DMARC는 검증 여부에 관심 없다. DMARC는 검증되고 정렬되는지에 관심 있다. 앞은 통과, 뒤는 미스 — 그건 실패다.

범인은 보통 당신의 ESP

가장 흔한 실제 구성을 따라가 보자. news@yourcompany.com에서 어떤 플랫폼을 통해 마케팅 메일을 보낸다. 기본값으로 그 플랫폼은 자기 도메인으로 서명한다 — d=esp-provider.net이거나 그쪽이 소유한 공용 서명 도메인이다. 서명은 진짜고 모든 수신 서버에서 검증된다. 그리고 아무것과도 정렬되지 않는다. esp-provider.netyourcompany.com의 Organizational Domain이 아니니까.

DMARC 리포트가 dkim=fail로 돌아온다. 원본 헤더를 열어 dkim=pass header.d=esp-provider.net을 보고는 리포트가 잘못됐다고 결론 내린다. 아니다. 헤더는 “서명이 검증됐나?”에 답하고, 리포트는 “정렬된 서명이 검증됐나?”에 답한다. 같은 단어, 다른 질문 — 그동안 쉬운 쪽만 읽고 있었던 거다.

해법은 거의 항상 같고, 에러 메시지만 봐서는 거의 절대 안 떠오른다. ESP가 자기 이름으로 서명하게 두지 마라. 제대로 된 플랫폼은 전부 custom DKIM을 지원한다 — CNAME 몇 개를 발행해서 selector를 당신 도메인 밑으로 옮기면, 이제 메일은 d=yourcompany.com으로 서명된다. 암호학적으로는 똑같다. 이제 정렬된다. 바꾼 건 서명의 유효성이 아니라 거기 적힌 이름이다.

relaxed 모드가 조용히 당신을 구하고 있다

DMARC가 잘 돌아가는 사람들 대부분은 relaxed alignment 덕분에 돌아가는 건데, 정작 relaxed alignment라는 게 있는 줄도 모른다.

relaxed(adkim, aspf 둘 다의 기본값)는 Public Suffix List로 계산한 Organizational Domain으로 매칭한다. 그래서 d=mail.yourcompany.comFrom: yourcompany.com과 정렬된다. 둘 다 같은 조직 도메인으로 수렴하니까. strict라면 거부한다 — strict는 라벨이 똑같길 원한다. PSL은 yourcompany.co.uk이 조직 도메인이고 co.uk은 아닌 이유이기도 한데, 멀티 라벨 접미사 밑에서 운영하는 순간 이게 중요해진다.

이걸 모르고 adkim=s로 넘기면, 서브도메인에서 서명되는 멀쩡한 메일을 깨뜨리게 된다. strict alignment를 켜는 사람들 대부분은 있지도 않은 문제를 풀면서 진짜 문제를 만든다.

그래도 alignment에 공들일 이유

신경 써야 하는 이유가 있다. 체크리스트들이 건너뛰는 부분이다.

DMARC에서는 정렬된 메커니즘이 하나만 통과하면 된다 — SPF든 DKIM이든. 그런데 왜 DKIM에 집착하는가? SPF는 누군가 메일을 포워딩하는 순간 죽기 때문이다. SPF는 접속하는 IP를 envelope sender에 대고 인증하는데, 포워더는 정의상 당신이 승인한 적 없는 접속 IP다. 첫 홉, SPF는 끝난다.

DKIM은 단순 포워딩을 견딘다. 서명이 메일과 함께 이동하고, 본문과 서명된 헤더가 온전히 도착하면 여전히 검증된다. 떠날 때 정렬돼 있었으면 도착할 때도 정렬돼 있다. 포워딩되는 메일 — 정상 메일은 끊임없이 포워딩된다 — 에 대해서는 정렬된 DKIM이 진짜 메일과 스팸함 사이에 서 있는 유일한 존재다. 이번 분기에 딱 하나만 고친다면 SPF 말고 DKIM alignment를 고쳐라. SPF는 정상 메일을 가장 많이 깨뜨리는 그 상황을 견디게 만들 방법이 없다.

짚고 갈 예외 하나: 메일링 리스트. 리스트 매니저는 Subject:를 고치고, 본문에 푸터를 박고, 때로는 From:을 통째로 갈아치운다. DKIM이 서명한 바이트가 바뀌니 body hash가 안 맞고, 서명은 검증에 실패한다 — 정렬이 아니라. 푸터가 body hash를 먹어버린 걸 alignment 문제로 디버깅하지 마라. 이 부류의 깨짐 때문에 ARC(RFC 8617)가 존재한다. 중간 시스템이 메일을 망가뜨리기 전에 자기가 본 인증 결과를 보증해주는 구조다. 패치 위의 패치지만, 정직한 패치다.

한 줄 요약

유효한 DKIM 서명은 “누가 서명했나?”에 답한다. DMARC는 다른 질문을 던진다 — “그 서명자가 From: 주소를 대변할 자격이 있나?” — 그리고 두 번째 질문의 답은 첫 번째 안에 들어 있지 않다.

딱 하나만 기억한다면: dkim=pass는 결승선이 아니다. header.d당신 것이어야 한다.

토론 참여

← 블로그로 돌아가기