SPF・DMARC・MTA-STSと整備を進めてくると、最後に残るのが「DKIMの鍵をいつ・どうやって交換するか」という運用の問題です。2024年以降、GmailやYahoo!メールが大量送信者へのメール認証を事実上必須化して以来、DKIMを設定すること自体は当たり前になりましたが、署名鍵を定期的に交換(ローテーション)する運用まで整えているケースは多くありません。
本記事では、メールを止めずにDKIM鍵を交換する手順を、実際に実行したコマンドと出力つきで解説します。検証はUbuntu 22.04+OpenSSL 3.0.2で行い、掲載している鍵はすべて本記事用に生成した使い捨てのものです(掲載後に破棄済み)。実運用の秘密鍵は、断片であっても絶対に公開しないでください。
この記事で分かること
DKIM鍵をローテーションすべき理由/セレクタ設計の考え方/無停止で切り替える5ステップ/OpenSSLでの鍵生成とTXT登録値の作り方(実行例つき)/旧鍵の失効(revoke)のタイミング/RFC 8463(Ed25519)二重署名の先行導入
なぜDKIM鍵のローテーションが必要か
DKIMの秘密鍵が漏えいしても、TLS証明書のような失効通知の仕組みはなく、漏えいした事実そのものを検知する手段がほぼありません。攻撃者が秘密鍵を入手すると「正規のDKIM署名つきのなりすましメール」を作れるため、DMARCでは止められなくなります。DMARCやBIMIの信頼はDKIM鍵の健全性の上に成り立っている、と言い換えられます。
また、メールサーバーの移設や運用委託先の変更、担当者の異動など、秘密鍵に触れた人と環境は時間とともに増えていきます。M3AAWGなどの業界ベストプラクティスで定期的な鍵交換が推奨されているのはこのためです。交換周期に統一基準はありませんが、半年〜1年に一度の定期交換を目安にしつつ、漏えいの疑いや担当交代があれば即時に交換できる手順を平時に用意しておくことが重要です。
前提:セレクタの仕組みがあるから無停止で交換できる
DKIMの公開鍵は セレクタ._domainkey.ドメイン のTXTレコードで公開され、署名ヘッダの s= でどの鍵を使ったかが示されます。セレクタが異なれば複数の公開鍵を同時に公開できるため、「新旧の鍵を並行公開 → 署名側だけ切替 → 旧鍵を後から削除」という無停止の交換が可能になります。
セレクタ名は s202608 のような日付ベースにしておくと、いつ作った鍵かが名前だけで分かり、棚卸しが楽になります。default のような固定名を使い回すと、この並行公開ができません。
無停止ローテーションの5ステップ
- 新しい鍵ペアを生成する(新セレクタ名を付ける)
- 新セレクタのTXTレコードを公開する(この時点では旧鍵で署名を継続)
- DNSの反映を確認する(digで新セレクタが引けること)
- MTAの署名設定を新セレクタに切り替える
- 猶予期間を置いて旧鍵を失効(revoke)し、削除する
実行例:RSA 2048の鍵生成からTXT登録値まで
以下はUbuntu 22.04+OpenSSL 3.0.2で実際に実行した結果です。
$ openssl version
OpenSSL 3.0.2 15 Mar 2022 (Library: OpenSSL 3.0.2 15 Mar 2022)
# 秘密鍵の生成(セレクタ名をファイル名に)
$ openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out s202608.rsa.key
$ chmod 600 s202608.rsa.key
# TXTに載せる p= 値(公開鍵のDERをbase64化)
$ openssl pkey -in s202608.rsa.key -pubout -outform DER | openssl base64 -A
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsmGsTwMA32rDmD15ly9VS7tFg3r8mcBdthmbZZKJSLAqPrRpl0l/4gezPc586wY6gwAe81oKz/0g8rOCOk3CKyuW5XWtn2V58R4IXqMOOrOhz3cHSRdoFNkU3xgzeqb/KuMFRomIfdkwZtnrdy8se/aic6TejmMwlrd2534ycrDAzkh3ELesE5lJ8gqhTkXOtBp/B9zfOCs69l9Em/wcdhpLUHywaEmFZdJWNQ29Bp30nDm4lFy2EHyVuuawxy4Q/3pwAQfQuFn3FoKtxrCJ/mRdXcFZPnKRVBoCTmexU5z7/FIT+NRy2Bxtm7fpEN6PHNaS1uFiwg0r9vrvmt1u1QIDAQAB
この出力(392文字)をそのまま p= に入れ、次の内容でTXTレコードを登録します。
s202608._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkq...(上の392文字)"
注意点として、DNSのTXTレコードは1文字列あたり255バイトまでという制約があります。RSA 2048の p= は392文字あるため、DNSサービスの管理画面では自動分割されることが多いものの、BINDのゾーンファイルを直接書く場合は "前半" "後半" のように文字列を分けて記述します。
鍵ペアが正しく機能するかの確認
DNS登録の前に、生成した鍵で署名→検証のラウンドトリップを確認しておくと確実です。
$ printf "dkim rotation test message" > msg.txt
$ openssl dgst -sha256 -sign s202608.rsa.key -out msg.sig msg.txt
$ openssl pkey -in s202608.rsa.key -pubout -out s202608.rsa.pub.pem
$ openssl dgst -sha256 -verify s202608.rsa.pub.pem -signature msg.sig msg.txt
Verified OK
DNS公開後は dig +short TXT s202608._domainkey.example.com で新セレクタが引けることを確認し、MTA側(OpenDKIMなら KeyTable / SigningTable)のセレクタを切り替えます。切替後に自分宛てへテストメールを送り、Gmailの「メッセージのソースを表示」などで dkim=pass かつ s= が新セレクタになっていることを確認してください。
旧鍵はいつ消すか:猶予期間とrevoke
切替直後に旧セレクタのTXTを消してはいけません。転送や再送、グレイリスティングなどにより、切替前に署名されたメールが数日遅れて検証されることがあるためです。目安として1〜2週間は旧セレクタを残します。
削除の前段階として、RFC 6376で定義されている「p= を空にする」方法があります。これは「このセレクタの鍵は失効した(revoked)」という明示的な意思表示で、単なるレコード削除よりも状態が明確になります。
s202601._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p="
Ed25519(RFC 8463)は二重署名で先行導入する
RFC 8463で追加されたEd25519-SHA256署名は鍵が小さく、TXT登録値もRSA 2048の392文字に対して44文字で済みます。ただし受信側の検証対応は依然としてRSAが主流のため、Ed25519単独への切替は推奨できません。導入するならRSAと並行して両方で署名する「二重署名」が定石です。検証側は対応している署名だけを検証し、未対応のアルゴリズムの署名は無視するため、二重署名によって到達性が下がることはありません。
# Ed25519鍵の生成
$ openssl genpkey -algorithm ED25519 -out s202608e.ed.key
$ chmod 600 s202608e.ed.key
$ openssl pkey -in s202608e.ed.key -pubout -out s202608e.ed.pub.pem
# 公開鍵のSPKI DERは44バイト。RFC 8463のp=は「生の32バイト」のbase64なので末尾32バイトを取り出す
$ openssl pkey -in s202608e.ed.key -pubout -outform DER | wc -c
44
$ openssl pkey -in s202608e.ed.key -pubout -outform DER | tail -c 32 | openssl base64 -A
hoyrl3BMMWASKRgAHmIXQ4IJWXReY5xgYQ+wcEmOTwo=
# 署名・検証のラウンドトリップ
$ openssl pkeyutl -sign -inkey s202608e.ed.key -rawin -in msg.txt -out msg.esig
$ openssl pkeyutl -verify -pubin -inkey s202608e.ed.pub.pem -rawin -in msg.txt -sigfile msg.esig
Signature Verified Successfully
TXTレコードはRSAと別のセレクタで公開します。
s202608e._domainkey.example.com. IN TXT "v=DKIM1; k=ed25519; p=hoyrl3BMMWASKRgAHmIXQ4IJWXReY5xgYQ+wcEmOTwo="
OpenDKIMで二重署名する場合は、KeyTableにRSA用とEd25519用の2エントリを作り、SigningTableで同一送信元に両方のキー名を割り当てます。設定後はテストメールのヘッダに2つの DKIM-Signature(a=rsa-sha256 と a=ed25519-sha256)が付与され、両方 dkim=pass になることを必ず確認してください。
この記事のコマンドをまとめたスクリプト
ここまでの手順――鍵の生成、TXTレコードの組み立て、署名・検証の自己診断、失効レコードの出力――を1本のシェルスクリプトにまとめてGitHubに置いてあります。bashとOpenSSL 3.x以外の依存はありません。
dkim-rotate — DKIM鍵ローテーション用スクリプト(GitHub)
# 新しい鍵を作り、DNSに登録するTXTレコードをそのまま出力
$ ./dkim-rotate.sh gen s202608 rsa
# Ed25519側(RFC 8463の生32バイトを正しく取り出す)
$ ./dkim-rotate.sh gen s202608e ed25519
# DNSに入れる前に鍵ペアを自己診断
$ ./dkim-rotate.sh verify s202608.rsa.key
# 公開後の反映確認
$ ./dkim-rotate.sh check s202608 example.com
# 旧セレクタの失効レコードを出力
$ ./dkim-rotate.sh revoke s202601 example.com
Ed25519の p= はSPKI DERではなく生の32バイトをbase64化する必要があり、ここを間違えると受信側で検証に失敗します。スクリプト側で正しい方を取り出すようにしてあります。同梱の .gitignore で *.key や *.pem を除外していますが、秘密鍵をリポジトリに置かない運用は必ず守ってください。
運用チェックリスト
- セレクタは日付ベースで命名し、固定名の使い回しをやめる
- 秘密鍵は
600権限で保管し、Gitリポジトリや共有ストレージに置かない - 交換周期の目安は半年〜1年。担当者の交代・漏えいの疑いがあれば即時交換
- 切替後も旧セレクタを1〜2週間は残す(遅延検証への対策)
- 削除の前に
p=空でrevoke状態を明示する - 切替後の数日はDMARC集計レポートでDKIM失敗(fail)が増えていないか監視する
最後の項目について補足します。DMARCの集計レポート(RUA)は毎日gzipやZIPで届くXMLなので、目視で追うのは現実的ではありません。鍵の切替後にDKIMのfailが増えていないかを送信元IP単位で確認するために、Python標準ライブラリだけで動く集計スクリプトを書いてGitHubに置いてあります。外部パッケージのインストールは不要です。
dmarcsum — DMARC集計レポート(RUA)の集計スクリプト(GitHub)
なお、レポートの読み方には落とし穴があります。policy_evaluated と auth_results にはどちらも dkim / spf という同名の要素があり、取り違えると原因を誤診します。前者はアライメントまで含めたDMARCとしての評価結果、後者は生の認証結果です。鍵を切り替えた直後に「SPFはpassなのにDMARCはfail」という結果が並んでいる場合、DKIMの問題ではなくアライメントの問題を見ている可能性があります。
関連記事:メール認証の整備を進める
DKIMの署名はDMARC・BIMIの土台です。未整備の項目があれば以下も参照してください。
まとめ
DKIMの鍵交換は、セレクタの並行公開を使えばメールを止めずに実施できます。「日付ベースのセレクタで新鍵を公開 → 署名を切替 → 猶予期間の後にrevoke」という流れを一度手順化してしまえば、次回からは短時間の定型作業です。鍵の健全性はDMARC・BIMIを含むメール認証全体の信頼の土台なので、定期的な棚卸しに組み込むことをおすすめします。
最終更新:2026年8月
