DKIM signing is surprisingly not about proving your email is legitimate to the recipient; it’s about proving it’s legitimate to the receiving server.
Let’s see this in action. Imagine you’re sending an email from sender@example.com to recipient@otherdomain.com.
# On your sending mail server (e.g., Postfix, Sendmail)
# Assume 'opendkim' is installed and configured to sign for 'example.com'
# A typical outbound email might look like this (simplified headers):
#
# From: Sender Name <sender@example.com>
# To: Recipient Name <recipient@otherdomain.com>
# Subject: Test Email
# Date: Tue, 15 Oct 2024 10:00:00 -0700
# Message-ID: <some-unique-id@example.com>
#
# This is the body of the email.
When your mail server processes this for sending, opendkim (or your chosen DKIM signing agent) intercepts it. It takes the headers and body, applies a cryptographic hash (usually SHA256), and then encrypts that hash using your private key. This encrypted hash becomes the DKIM-Signature header.
# The mail server adds this header *before* sending:
#
# From: Sender Name <sender@example.com>
# To: Recipient Name <recipient@otherdomain.com>
# Subject: Test Email
# Date: Tue, 15 Oct 2024 10:00:00 -0700
# Message-ID: <some-unique-id@example.com>
# DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=example.com; s=selector1;
# t=1697370000; bh=some_base64_encoded_hash_of_body_and_selected_headers;
# h=From:To:Subject:Date:Message-ID; b=some_base64_encoded_signature_of_bh_and_h;
#
# This is the body of the email.
The DKIM-Signature header contains several key pieces:
v=1: The DKIM version.a=rsa-sha256: The signing algorithm.c=relaxed/simple: The canonicalization method for headers and body.relaxedallows minor whitespace and line ending changes,simplerequires exact matches.d=example.com: The signing domain.s=selector1: The selector, a short string that helps the recipient find the correct public key.t=1697370000: A timestamp (optional but good practice).bh=...: The hash of the canonicalized body.h=From:To:Subject:Date:Message-ID: The list of headers that were included in the signature calculation.b=...: The actual signature, encrypted with your private key.
The receiving server, otherdomain.com, sees this DKIM-Signature header. It uses the d (domain) and s (selector) values to look up your public key. It queries DNS for a TXT record at selector1._domainkey.example.com. This record contains your public key and information about the signature.
The recipient server then:
- Verifies the signature’s integrity using the public key and the
bh(body hash) andh(headers) fields. - Checks if the
d(domain) in the signature matches theFrom:header’s domain. - Checks if the
s(selector) is valid. - Calculates its own hash of the email’s body and selected headers using the same canonicalization methods (
c=relaxed/simple). - If its calculated hash matches the
bhin the signature, and the signature decrypts correctly with the public key, the email is considered DKIM-valid forexample.com.
This process doesn’t guarantee the email is from a legitimate sender, but it guarantees that the email hasn’t been tampered with since it was signed by the server at example.com. It’s a crucial piece of the modern email authentication puzzle, working alongside SPF and DMARC.
To generate keys and set up signing, you’ll typically use a tool like opendkim-genkey.
First, create a directory for your keys:
sudo mkdir -p /etc/opendkim/keys/example.com
cd /etc/opendkim/keys/example.com
Then, generate the key pair. You need to specify a selector (e.g., mail or selector1) and your signing domain (example.com).
sudo opendkim-genkey -b 2048 -d example.com -D . -s mail
-b 2048: Specifies a 2048-bit key length, which is a good balance of security and performance.-d example.com: The domain for which you are signing.-D .: The directory to output the keys into (the current directory).-s mail: The selector name. This will result in files namedmail.private(your private key) andmail.txt(the public key DNS record).
Next, set the correct ownership and permissions for the private key so only the DKIM signing process can read it.
sudo chown opendkim:opendkim mail.private
sudo chmod 600 mail.private
chown opendkim:opendkim: Ensures theopendkimuser and group own the private key file. Your mail server’s DKIM milter will run as this user.chmod 600: Gives read/write permissions only to the owner, preventing any other user or process from accessing it.
Now, you need to publish the public key in your DNS. The content of the mail.txt file is what you’ll add as a TXT record. It will look something like this:
mail._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0f..."
"..."
"AQAB" ) ; ----- DKIM key mail for example.com
You’ll create a TXT record in your DNS zone for example.com with the name mail._domainkey. The value will be the quoted string from the mail.txt file. Some DNS providers automatically handle the quoting and concatenation of long strings.
Finally, configure your mail server (e.g., Postfix) to use the DKIM milter. In your main.cf (for Postfix), you’d add lines like:
# /etc/postfix/main.cf
smtpd_milters = inet:127.0.0.1:8891
non_smtpd_milters = $smtpd_milters
milter_default_action = accept
milter_protocol = 6
And in your OpenDKIM configuration file (e.g., /etc/opendkim.conf):
# /etc/opendkim.conf
Domain example.com
KeyFile /etc/opendkim/keys/example.com/mail.private
Selector mail
Canonicalization relaxed/simple
Socket inet:8891@localhost
Domain,KeyFile,Selector: These tell OpenDKIM which key to use for which domain.Canonicalization: Crucial for compatibility.relaxed/simplemeans headers are canonicalized withrelaxedrules and the body withsimplerules.Socket: This defines how the milter listens for connections from the mail server.inet:8891@localhostmeans it listens on TCP port 8891 on the local machine.
After restarting your mail server and the OpenDKIM service, outbound emails from example.com will be signed.
The next hurdle you’ll likely face is understanding how DMARC policies interact with DKIM failures, especially when dealing with forwarded emails.