DKIM Message Signing in Postfix

This page assumes you have either a working Postfix Null Client MTA or a working Postfix SMTP Submission Server.

This page also assumes you are familiar with the basic Mail Server Technology as defined on the main Mail Server introductory page.

Finally this page assumes some familiarity with DNS zone files and records.


DKIM stands for DomainKeys Identified Mail and is defined in RFC 6376 (September 2011) updated by RFC 8301 (January 2018) and RFC 8463 (September 2018).

DKIM is used to give confidence that an e-mail message was sent using a SMTP server authorized to send e-mail on behalf of the mailbox domain the message claims to be from.

A private/public key pair is generated. The private key resides on the SMTP server and the public key is stored in a DNS TXT record in the zone for the mailbox domain.

When the SMTP server sends an e-mail on behalf of the mailbox domain, it uses the private key to create a cryptographic signature of the message and attaches this signature to the message before sending the message to another SMTP server.

The recipient and any server along the way can verify the message was not modified since it was signed by retrieving the public key from the DNS record.

The primary problem this solves is message spoofing, where the From: header is forged to make the message appear as if it came from someone it did not come from. It can not provide the same kind of confidence as PGP or S/MIME provides but it provides far more confidence than no signature provides and it does not require any action or technical know-how from the sender to implement. DKIM also lends itself very well to software generated e-mail, such as notifications sent by web applications.

Lesser Technical Description

Alice writes an e-mail to Bob, and clicks the Send button in her e-mail client. At this point, there is no signature.

Alice’s e-mail client sends the necessary authentication to the Submission server and the message is transmitted to the submission server. At this point, there is no signature.

The submission server, having received the necessary authentication information, signs the message using a private key that exists on the submission server. This key is only unique to the mailbox domain, it is not unique to individual users. Now there is a signature.

The submission server sends the message to Bob’s MX server. The spam filter on Bob’s MX filter sees that the message is signed, and can verify the signature against a public key it retrieves from DNS. It has confidence the message was not forged, it will have a lower spam score than if it was not signed, making it less likely to be flagged as spam. It gets delivered to Bob.

Bob’s e-mail client sees that the message is signed, and validates the signature. It lets Bob know the signature validates. Bob now has higher confidence the message really did come from Alice.

Bob realizes it is possible someone stole Alice’s login credentials, or a software bug on the submission server allowed someone to send the message as Alice without authentication, or someone with administrative privileges on the submission server forged the message. He does not have the same confidence as if the message was signed using a private key that Alice and only Alice has access to, but he has more confidence than if the message was not signed.

Bob does however have very high confidence the message was not modified since it left the submission server. An attacker between the submission server and Bob’s e-mail client could not have modified the message, or the signature would not validate.

Greater Technical Description

Every Private/Private keypair has two identifying components. The first is the mailbox domain it is valid for, and the second is called a selector. The selector is an arbitrary string, label if you will, that allows a mailbox domain to have several different valid private/public keypairs - one for each MTA that sends messages on behalf of the mailbox domain.

This selector (label) is part of the signature and is used by those validating the message to request the correct public key from the DNS system.

If the mailbox domain is example.net and the message signature says the selector used was funky2018, those wishing to verify the signature would do a DNS request for the TXT record associated with funky2018._domainkey.example.net. to retrieve the public key needed to validate the signature.

Unless you are implementing DMARC there is no requirement to use DKIM at all, let alone on every server that sends messages on behalf of your mailbox domain. However, it is best practice to do so.

Message signing should happen at the MTA where a valid messages first enter the e-mail system. If you use a gateway MTA, messages should be signed before being relayed through the gateway MTA.

2048-bit RSA

RFC 6376 defined two signature algorithms to be used with DKIM, RSA-SHA1 and RSA-SHA256. It recommended using a 1024-bit key size. In 2011 when RFC 6376 was ratified, a 1024-bit RSA key had already been cracked once by academic researchers. Once a signing algorithm has been cracked, stop using it. Cracking it will become much faster due to both optimization in the code and methods used to crack it and increasing raw computational power.

Why they chose 1024-bit as the recommended key size I really do not know, but I suspect it was because the BIND file format frequently used for DNS has a line length limit, and 1024-bit is the largest with a public key would fit inside that limit. Honestly that is a pretty stupid reason to recommend an insecure key size, especially since BIND format specifically provides a way to break up a really long single record into multiple lines.

One thing RFC 6376 argues is ‘Keys can be replaced on a regular basis; thus, their lifetime can be relatively short’. Unfortunately while that statement is technically true, since certificates with an expiration are not used the reality is that keys often remain in service for multiple years.

The RFC also states ‘The security goals of this specification are modest compared to typical goals of other systems that employ digital signatures’ which is a very flawed security philosophy. A cracked DKIM key allows the attacker to apply social engineering techniques that pass validation checks and appear to be genuine. Security is only as strong as the weakest link, and the weakest link is almost always human psychology. DKIM provides a mechanism for increasing the confidence in a message, but if the key is cracked, that increased confidence becomes a danger.

I believe at this point in time an attacker with modest resources, such as the Kremlin-linked ‘Internet Research Agency’ (trust me, they are more than just a propaganda machine), can brute-force an RSA 1024-bit key in under six months. RSA 2048-bit however would take multiple decades. Anything that uses RSA should not use anything smaller than 2048-bit or trust anything smaller than 2048-bit.

The RFC 8301 update to DKIM does deprecate RSA-SHA1 and any RSA key size smaller than 1024-bit. It should go farther. It suggests using 2048-bit keys but does not require them, and it says validators should continue to validate 1024-bit keys. I strongly disagree. Anything less than 2048-bit should not be considered secure, just like no Certificate Authority will sign an x.509 RSA certificate that is less than 2048-bit.

Ed25519-SHA256 appears to be the near future of DKIM but there is not yet enough support for that signing algorithm in software that validates DKIM signatures. For the next few years, we still need to use RSA-SHA256 with a 2048-bit private key.

OpenDKIM as packaged for LibreLAMP has been patched to default to 2048-bit key pairs and the configuration file by default rejects RSA keys smaller than 2048-bit.

OpenDKIM as packaged for LibreLAMP has been patched to be able to validate Ed25519-SHA256 signatures and probably can sign with Ed25519-SHA256 but I have not tried.

Installing OpenDKIM

Assuming your RHEL / CentOS 7 install uses the LibreLAMP package repository, you can install OpenDKIM with the command:

[root@host ~]# yum install opendkim

That should bring in OpenDKIM and any dependencies it needs.

Signing Key Generation

First thing we need to do is generate our 2048-bit RSA key pair.

It is common practice to create a directory named after the mailbox domain you are signing messages for within the /etc/opendkim/keys directory. So for example, if you are going to be signing messages on behalf of example.net:

[root@host ~]# mkdir /etc/opendkim/keys/example.net
[root@host ~]# chown root:opendkim /etc/opendkim/keys/example.net

Next, decide what you want to use for selector. It should be short, preferably using only alphabet letters but ending in the current four digit year (or the next four digit year if it is close to the end of the calendar year). Appending the year makes it easier to smoothly rotate your signing keys once a year as you should.

Some examples of good selector names: flower2019, cake2019, juniper2019, etc.

The selector you use will be public, it will be part of the domain name used to query the public key. So do not use something obscene or offensive, if you were planning to.

Once you have chosen a selector, you can generate a keypair:

[root@host ~]# /usr/sbin/opendkim-genkey -D /etc/opendkim/keys/example.net/ -d example.net -s flower2019
[root@host ~]# chown root:opendkim /etc/opendkim/keys/example.net/flower2019.private
[root@host ~]# chmod 640 /etc/opendkim/keys/example.net/flower2019.private

The -D /etc/opendkim/keys/example.net/ switch specifies where the key pair is to be installed.

The -d example.net switch specifies the mailbox domain the keys are being generated for. My suspicion is that switch only impacts the comment in the generated DNS record comment and does not actually matter but I would have to read the opendkim-genkey script more carefully to know for sure.

The -s flower2019 switch indicates that flower2019 is the chosen selector name. Do not use the same selector name for different servers, each MTA that signs messages for a mailbox domain needs to have its own unique selector.

The chown root:opendkim /etc/opendkim/keys/example.net/flower2019.private command sets the proper ownership of the private key.

The chmod 640 /etc/opendkim/keys/example.net/flower2019.private command makes sure only the root user is allowed to overwrite the file with the private key, and only the root user and members of the opendkim group are allowed to read the contents of the private key. This is important.

The DNS Record

Before anything else is done, the public key needs to be added to the zone file for your mailbox domain.

In the above example using example.net as the mailbox domain and flower2019 as the selector, a BIND zone file compatible entry containing the public key will exist in the file /etc/opendkim/keys/example.net/flower2019.txt.

Make sure that gets into your zone file and uploaded to your authoritative DNS server.

Before continuing, make sure you can retrieve the record from DNS:

[user@host ~]$ dig TXT +short flower2019._domainkey.example.net.

        [example output below]

"v=DKIM1\; k=rsa\; p=" "" "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8DiO35SBX8w1rTDp" "" "5xIRtm
1UhyBJeiOmvsXc/1WYxOnnCFJ1GAUIXED+juVPHR33LvE8VS+V6iNl" "" "4b2kijDIpVZdn1jHC4GIZXBnfPCejZNXClnkvin
FSZdky4d4rA/MczPxYZuc" "" "pKrdePSmlcyeCzHgneSKDrS1SPj3rfXaBsPPqgMPKuNG57pSqQaftjDrBYo/" "" "t4ckHg
YyXhA1PR+1I7nnMCpUfG5FvhhtHtFdBEwsEhpa8eCGapI9sIyopAhi" "" "7XKziJ+pO5BX+hrBD4+VejZ1ZwDzBMDNpoM2IQh
4QzRMnIF574/aaMGuLZnJ" "" "jv3rx1uEpAnTqauagKq3s4gchQIDAQAB"

Obviously replace flower2019 with your selector and example.net with your mailbox domain. And obviously the gobble guck in the output will be different, but should be something similar. If you do NOT get a result, then either the zone file was not correctly modified or has not propagated yet.

OpenDKIM Configuration File

Once the DNS record is working, you can configure the daemon. For a basic setup with OpenDKIM signing e-mail for one mailbox domain, there are three parameters in the configuration file that must be modified.

The configuration file is located at /etc/opendkim.conf:

Domain
Change this to your mailbox domain.
KeyFile
Change this to point to the full path for the private key you generated
(/etc/opendkim/keys/example.net/flower2019.private in the example)
Selector
Change this to reflect the name of the selector you chose (e.g. flower2019)

For the many other options, see the OpenDKIM documentation. I recommend getting it working first before changing other options. For configuring OpenDKIM to sign e-mail for more than one mailbox domain, see the OpenDKIM documentation. Again, get it working with one mailbox domain first.

Now, you can start and enable the OpenDKIM daemon:

[root@host ~]# systemctl enable opendkim.service
[root@host ~]# systemctl start opendkim.service

You can check the status with:

[root@host ~]# systemctl status opendkim.service

Postfix Integration

Now we can tell Postfix to pass mail through it, messages from the mailbox domain OpenDKIM is configured to sign messages for should be signed.

Edit you /etc/postfix/main.cf file and add the following:

# OpenDKIM
smtpd_milters           = inet:127.0.0.1:8891
non_smtpd_milters       = $smtpd_milters
milter_default_action   = accept

Reload Postfix:

[root@host ~]# postfix reload

If everything is working properly, Postfix will now send messages through that milter and messages from your mailbox domain will now be signed.

Key Rotation

It is a good idea to generate a fresh private key once a year. This reduces the potential impact of a compromised private key.

You will want to use a new selector, which is why I put the four digit year at the end of selector names, I can just increment it and do not need to get creative.

End of November / beginning of December, generate a new keypair using the four digits of the coming year at the end of your selector. With the flower2019 selector used on this page, during end of November or early December of 2019 I would increment it to create a keypair using flower2020 as the selector. Add the DNS for the new selector to your mailbox domain zone, as described earlier.

Once you have verified the new public key is resolving in DNS you can update the /etc/opendkim.conf file and change the KeyFile and Selector parameters to reflect the new private key and selector. Restart the OpenDKIM daemon.

Go ahead and leave the old public key associated with the old selector in DNS for a few weeks, so that anyone who received e-mail signed with the old key can still have their e-mail client validate the signature if they want it verified.

DKIM really is intended for validation on servers and not within clients, twenty-four hours of grace is sufficient for that. However it is still nice to e-mail clients that do want to validate to keep the old public key in DNS at least for a few weeks after it goes out of signing service.

However, after a few weeks of signing with the new key, delete the public key from the old selector from DNS.

As long as the old public key remains in DNS it is still valid. That means if it was compromised, it can be used to fraudulently sign e-mail that will validate. So do remember to remove the old public keys from your DNS after a few weeks of grace period, else there is no point in having generated the new one.

Added Security with DNSSEC

The DKIM specification does not require DNSSEC but it should.

Please, use a DNS service that signs your zone file so clients with DNSSEC enforcing recursive resolvers will not receive fraudulent public keys via DNS.

Certificate Authorities are not used with DKIM. Without DNSSEC, the public key a DKIM validator uses to validate a signature requires blind trust that the easy to alter DNS response has not been altered. With a DNSSEC signed zone, blind trust is not required.