Prevent spoofing using DKIM

Email sender spoofing is the act of pretending to be in control of someone else’s email address. This is a common problem with phishing. Often scammers send emails with a sender address of something@paypal.com and hope that the recipient falls for it and trusts them. In fact SMTP does not care which sender address you send. Many mail service providers enforce that you send emails only using your own email address. But some do not. And spammer and scammers obviously could not care less.

Spoofing case without DKIM

So roughly ten years ago a new method was conceived that added a cryptographic signature to the header of an email that the recipient could check to verify the authenticity of the sender and the integrity of the email. The signature is created using a private key that only the sending mail server has. It can then be verified by the recipient by downloading the corresponding public key from the DNS zone of the sending domain and running a signature check. This works very similar to PGP or S/MIME signing – just on a domain level. Your mail server can sign all outgoing emails automatically. The method used nowadays is called Domain Keys Identified Mail – or short: DKIM.

Let’s take an example. I have just sent an email from GMail to my personal email account on my own mail server. Google uses DKIM signing so the email got this additional header from Google’s mail servers:

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20161025;
 h=mime-version:from:date:message-id:subject:to;
 bh=/FpkZfBuKR0WWcH2fFcr9M4qgX4Z4+/0dX4dpqycK28=;
 b=fZ4Efy1TuXAl1ho2twkEuNiVP8k5GRKqlol/f/dTawaxciAv1bwinkbu…

I need Google’s DKIM public key to verify that signature. It is stored in their DNS zone as a TXT record of “20161025._domainkey.google.com”. The “20161025” is the key selector that is mentioned in the signature as “s=20161025“. You can use any number of keys as long as you create the signatures with the matching private key.  The “_domainkey” part is the standard subdomain for DKIM keys. So let’s get that TXT record:

dig +short 20161025._domainkey.google.com txt

This returns…

"k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXNZF1j8sJPDleRjf9SPBNem0ik58kF1ilC1nUgKAttl9v7FX9hXJXPmLNhVtSKVZ8yruaeOZLeIxtgtk1s81zzIE5Mj0AiGn2wlFt4kYfqlDfYe95YLQHjynu4i7vj1Tj" "ksf62btcCbL+3XhbK+oD5PlqYhXHWuzoKoEp5L4lCihgkONvU/oy7NNeE6quqfF/y0YSLwF2WVA2Kd8L6R0Ar2dYT/3wZCFknI7xhvPqh9HNcIWBELGPwtXcsHbX1wvBlCgNQAUcdJrf2YWzAwqmZ564/1ipL1IMk1nafPJk75ktumVNz6ORuIn3jbZWp9rRpnaeI9cu/8KfSKH2EY9QIDAQAB"

That is the public key that I can use to verify the signature. An automated check can be done using the “opendkim-testmsg” tool as described later. I can run it and paste the entire email including headers and body into it. If it doesn’t complain with an error message then the signature is correct.

Sounds good? Then let’s implement that for your email domain, too.

Creating a keypair

As explained above you need a private key that your mail server will use and a public key that gets added to your DNS zone. rspamd can create DKIM keys already. You may want to install “dig” though which allows to query DNS records. It works similar to “nslookup” but is more versatile.

apt install dnsutils

rspamd has its built-in DKIM signing module enabled by default.

If you put your key file into /var/lib/rspamd/dkim/ using a certain naming scheme it will pick it up automatically. Create that directory to store keys in:

mkdir /var/lib/rspamd/dkim
chown _rspamd:_rspamd /var/lib/rspamd/dkim

Create your keypair:

rspamadm dkim_keygen -d example.org -s 2019122401

The selector (-s) I chose is 2019122401 because that’s the day I created it. The first (01) key on 2019-12-24. It doesn’t matter though – you can name it anything you want. If you are lazy and unconcerned you could even use “dkim” as the selector and later spare some work by not needing DKIM maps that define which key is supposed to be used for each domain. “dkim” is the default selector if you do not use maps. But you will probably some day want or need to replace the key so I recommend you rather use maps as explained further below. It gives you more flexibility and is pretty easy to do.

The output will look like this:

-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQE…
-----END PRIVATE KEY-----
2019122401._domainkey IN TXT ( "v=DKIM1; k=rsa; "    "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCZ8e4RtsvuvsgXyo3Jr63twdx3Zm2RtnYPf87wba5ZnFP0sQIagjJ0R3WL+djKhM5EteBeboTeaflLR+AUKWJdQnL5jzGil+yWThH5C+ixhkdFZPdabkxBji+3r6uRxJKBsN0gy2/+Gp4sGs7lmFgioki9OLgQnzzFh2nE/MSACwIDAQAB" ) ;

The first part is the private key. And that includes the “…BEGIN…” and “…END…” lines. This key must be kept secret and will only be used by your mail server to sign outgoing emails.

The second part is the DNS record you need to add to your domain’s DNS zone. Let’s start with that.

Adding the DNS record

Before you start signing your emails you must make sure that the public key is properly present in your DNS zone for the domain you are sending emails from. Otherwise the recipient will be unable to verify the signature and may incorrectly assume that the email was spoofed.

Take a look at the TXT record. It will look something like this:

2019122401._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; " "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxenHupkYLPmFMbJjV9dQICKUl2xH/aexSRUwCuw7TJ9dkddqIN+6tyw4VKhnW8R0/UlbzlSFLmVgMU0uUkwTtVqyDHhtSU7LV/SkVYmUst4dTUF1r+8PvhAm7vobMYKdwvRsOq27ABtZc8P4oU2XXHqqa6LU8s4sNxs12hLW9swIDAQAB" ) ;

If you are running your own DNS server you should be able to copy this entire file and put it into your DNS zone. However if your internet provider offers you just a web interface to manage your domains then create a new TXT record with a host name of “2019122401._domainkey” in your domain and put the string within the double-quotes into it as the value. In my example:

2019122401._domainkey ➠p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxenHupkYLPmFMbJjV9dQICKUl2xH/aexSRUwCuw7TJ9dkddqIN+6tyw4VKhnW8R0/UlbzlSFLmVgMU0uUkwTtVqyDHhtSU7LV/SkVYmUst4dTUF1r+8PvhAm7vobMYKdwvRsOq27ABtZc8P4oU2XXHqqa6LU8s4sNxs12hLW9swIDAQAB

Be aware that the string you got contains two strings “…” + “…” that must be merged into one to work. (The syntax with quotes is meant for a DNS zone file if you run your own name server like bind.) There must usually not be any quotes in the record data. Depending on your ISP it may take a while until the new record is visible on the internet. You can use dig to verify that:

dig +trace 2019122401._domainkey.example.org txt

If you get the TXT entry like as follows then you are ready to enable DKIM signing in rspamd for that domain:

TXT "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxenHupkYLPmFMbJjV9dQICKUl2xH/aexSRUwCuw7TJ9dkddqIN+6tyw4VKhnW8R0/UlbzlSFLmVgMU0uUkwTtVqyDHhtSU7LV/SkVYmUst4dTUF1r+8PvhAm7vobMYKdwvRsOq27ABtZc8P4oU2XXHqqa6LU8s4sNxs12hLW9swIDAQAB" "" from server foo.bar in 24 ms.

Enabling DKIM maps in rspamd

As explained above it is advised to use DKIM maps. It’s nothing fancy. Just a simple file defining which selector you want to use for a certain domain. rspamd will assume that your selector is always “dkim” unless specified otherwise in a map. If you used “dkim” then you may get into trouble when you later want to replace your key. DNS is a sluggish system and propagating a new DKIM public key may take a day. Emails signed with a newer key may get rejected while the DNS record is not yet known everywhere in the world.

Using maps is simple. First we need to change the selector_map setting of the dkim_signing module. To do that create a new file in /etc/rspamd/local.d/dkim_signing.conf and make it contain just these two lines:

path = "/var/lib/rspamd/dkim/$domain.$selector.key";
selector_map = "/etc/rspamd/dkim_selectors.map";

The configuration is pretty self-explaining. rspamd will look for the domain-to-key mapping in the dkim_selectors.map file. Create that file and make it contain this line:

example.org 2019122401

That’s all really. rspamd now knows that whenever it sees an outgoing email from anyone@example.org it will get the DKIM private key from /var/lib/rspamd/dkim/example.org.2018022301.key and use it to sign the email.

Reload the configuration:

service rspamd reload

This method works well if you just have a few domains that virtually never change. If you are rather serving random customer domains you should consider putting the keys into a Redis database instead as described in the documentation. There is not yet a way to manage DKIM keys in a database like MySQL.

Selectors?

Selectors may seem unnecessary. But you will need them once you want to replace your current signing key later. In that case you need to generate a new key using a different selector/name. Now that both DNS records are online you can make your mail server use the new signing key. You should leave the old key in place for a while.

Adding the domain key to rspamd

Take the private key that was created earlier (the multi-line string including “…BEGIN PRIVATE KEY…” and “…END PRIVATE KEY…“) and put it into a file at the location where rspamd will look for it:

/var/lib/rspamd/dkim/example.org.2019122401.key

The name of the file has to be  DOMAIN + dot + SELECTOR + “.key” like above. If you name the file incorrectly you will get an error in your rspamd.log file like “lua_dkim_sign_handler: cannot load dkim key /var/lib/rspamd/dkim/example.org.dkim.key“.

Make sure that only _rspamd can read it:

chown _rspamd /var/lib/rspamd/dkim/*
chmod u=r,go= /var/lib/rspamd/dkim/*

rspamd will automatically pick up the files and does not need to be restarted.

Send a test email

If you have another email account at another location then you could just send a test email there via your mail server. If you take a look at the received email it should have a DKIM header now like:

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.org;
	s=2019122401; t=1519400838;
	h=from:subject:date:message-id:to:mime-version:content-type:content-transfer-encoding;
	bh=kpYel1IlDvqXEUc0SyIpXbMte3XpQOCXHl+zTyHQvGc=;
	b=NEUyWUoeKE…
DKIM signature verification failed

To verify the signature install the opendkim-tools package, copy the entire test email (including headers and body), run opendkim-testmsg in your shell and paste the email (finish with CTRL-D).

If you get no output then the signature verified correctly. But if you get something like “opendkim-testmsg: dkim_eom(): Unable to verify” then double-check your DNS record.

You can also use websites like dkimvalidator.com, isnotspam.com or mail-tester.com service to verify that your signatures are working well.

SPF and DMARC

Adding DKIM signatures is a good first step. But you can take it further by telling receiving mail servers that they should not accept any email from your domain without a valid signature or from servers that you do no operate. There are two concepts that aim to help. The older SPF and the newer DMARC. Either of them means creating a machine-readable string in a predefined format and adding a TXT record to your DNS zone. Receiving mail servers can check those records and take your advice (as the domain owner) what to do if the criteria of the email are not met. It could accept the email anyway or flag it as spam or reject it altogether.

Let’s take a look at a typical SPF record:

"v=spf1 ip4:157.97.194.11 mx ~all"

What it means:

  1. this is an SPF record of version 1 of the standard (there is currently no other version)
  2. please accept emails from the IP address 157.97.194.11
  3. alternatively accept emails from any server that is mentioned in our domain’s MX record (the server(s) that receive email for your domain)
  4. any other email should be considered suspicious – it might be spam or worse

There are websites like SPFwizard that help you create your SPF string to add to your DNS domain. Keep in mind though:

  • You should know which mail servers send email from your domain. Do not forget to include mailing list or newsletter services that send in your name.
  • Start with “~all” to mark emails as spam that do not meet the criteria. If all goes well switch to “-all” after a few weeks if you like.
  • Note that forwarding emails from your domain may break SPF because suddenly the email appears to be coming from an IP address that is not authorized. This has been a common problem for mailing lists and is gradually being fixed by resending the email from the domain of the mailing list service.

I mentioned that DMARC is the newer standard. So why use SPF anyway? Because some email providers value your effort if you use SPF, too. Technically it’s sufficient to specify a DMARC entry. In my opinion restricting the IP addresses allowed to send is a little dangerous and a little inflexible. It is far more interesting to require that emails from your domain have a valid DKIM signature. Such a record may roughly look like:

"v=DMARC1; p=reject; adkim=s"

However to create a proper DMARC entry I suggest you use one of the web sites that aid you there and explain the restrictions and extra features.

16 thoughts on “Prevent spoofing using DKIM”

  1. Jay Gardner

    question on the DKIM text value… i’m reading in above that the text record for 2019122401._domainkey.example.org is simply the string (with the quotes included) as “p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxenHupkYLPmFMbJjV9dQICKUl2xH/aexSRUwCuw7TJ9dkddqIN+6tyw4VKhnW8R0/UlbzlSFLmVgMU0uUkwTtVqyDHhtSU7LV/SkVYmUst4dTUF1r+8PvhAm7vobMYKdwvRsOq27ABtZc8P4oU2XXHqqa6LU8s4sNxs12hLW9swIDAQAB”.

    However, i’ve also read in other places it should include the v=DKIM1 component, so “v=DKIM1;
    p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxenHupkYLPmFMbJjV9dQICKUl2xH/aexSRUwCuw7TJ9dkddqIN+6tyw4VKhnW8R0/UlbzlSFLmVgMU0uUkwTtVqyDHhtSU7LV/SkVYmUst4dTUF1r+8PvhAm7vobMYKdwvRsOq27ABtZc8P4oU2XXHqqa6LU8s4sNxs12hLW9swIDAQAB”

    In fact , look at the result of dig dkim._domainkey.twitter.com txt… it has the v=DKIM1 as part of the text record

    However, both seem to be accepted when run through a site like http://www.mail-tester.com/.

    Can you comment on whether either is acceptable or not? thanks

    1. The real question is whether big mail services will pass your mail. I just re-did my setup from scratch and GMail fails the DKIM signature unless you include the `v=DKIM1` specifier.

  2. Hi,
    Thanks for these guides. They helped a lot over the years.
    I tried researching for how to create DKIM for multiple domains to no avail.
    The setup is as follows; the server FQDN is mail.abc.com. But serving emails for abc.com, domain1.com & domain2.com.
    Happy new year

  3. rspamadm dkim_keygen -d abc.com -s 2019122401
    rspamadm dkim_keygen -d domain1.com -s 2019122401
    rspamadm dkim_keygen -d domain2.com -s 2019122401
    “Before you start signing your emails you must make sure that the public key is properly present in your DNS zone for the domain you are sending emails from. Otherwise the recipient will be unable to verify the signature and may incorrectly assume that the email was spoofed.”

  4. Hi,
    I seem to have set up everything correctly (I even redid it just to make sure)
    But rspamd keeps trying to load (mydomain).dkim.key instead of the real key (mydomain).0802202202.key, even though I’ve set it in the dkim_selectors.map file.

    I could not find an answer somewhere else so I hope anybody can help

  5. If you are allowing users to send as their aliases, do not forget to add the following line to /etc/rspamd/local.d/dkim_signing.conf:

    allow_username_mismatch = true;

    Otherwise, your mails will _not_ get signed, when sending as an alias.

  6. It might be worth bringing the google DKIM example up to date: the current key in use (September 2022) is 20210112. Either that, or mention that Google change their keys periodically and, to establish the currently valid key, send a test mail to your server from a gmail account.

  7. Hi,
    I have been running the mail server for some time. Everything works except for the DKIM signature. On my previous mail server the DKIM signature works. I have compared the configurations of both servers. Unfortunately I can’t find any difference. MXTOOLBOX tells me that everything is ok. Mail-Tester.com gives me only 9/10 points because the DKIM signature is missing. So probably a bug on my server and not in the DNS record.
    In /var/log/rspamd/rspamd.log my old server gives me the following output after a sent mail:

    2022-10-03 20:23:06 #921(normal) ; task; rspamd_task_write_log: id: , qid: , ip: 84.153.106.63, user: postmaster@server02.de, from: , (default: F (no action): [-1.36/15.00] [BAYES_HAM(-2.26){96.57%;},FROM_DN_EQ_ADDR(1.00){},MIME_GOOD(-0.10){text/plain;},ARC_NA(0.00){},ASN(0.00){asn:3320, ipnet:84.128.0.0/10, country:DE;},DKIM_SIGNED(0.00){server02.de:s=2022012001;},FREEMAIL_ENVRCPT(0.00){t-online.de;},FREEMAIL_TO(0.00){t-online.de;},FROM_EQ_ENVFROM(0.00){},MID_RHS_MATCH_FROM(0.00){},MIME_TRACE(0.00){0:+;},RCPT_COUNT_ONE(0.00){1;},RCVD_COUNT_ZERO(0.00){0;},TO_DN_ALL(0.00){},TO_MATCH_ENVRCPT_ALL(0.00){}]), len: 1782, time: 40.328ms, dns req: 1, digest: , rcpts: , mime_rcpts: <xxxxx@t-online.de

    On my new server this output is missing after a sent mail.
    I checked the points of the tutorial several times and completely purged rspamd once and then reinstalled it.
    When I compare the configuration with my old server I don't find any difference either.

    Does anyone else have an idea.

  8. Hi,
    In :
    *That’s all really. rspamd now knows that whenever it sees an outgoing email from anyone@example.org it will get the DKIM private key from /var/lib/rspamd/dkim/example.org.**2018022301**.key and use it to sign the email.*

    Should `2018022301` be replaced by `2019122401`?

    1. Christoph Haas

      Totally. Thanks for the hint. Fixed. You should think that such essential mistakes shouldn’t be there any more. 🙂

  9. Michael Fischer

    In case your mail domain is a subdomain your
    /etc/rspamd/local.d/dkim_signing.conf has to contain “use_esld = false;” like this:

    use_esld = false;
    path = “/var/lib/rspamd/dkim/$domain.$selector.key”;
    selector_map = “/etc/rspamd/dkim_selectors.map”;

    Otherwise rspamd will NOT sign your outgoing emails.

  10. Thank you again for this thorough set of clues.
    The most time-consuming task for me was in the gap between successfully installing / configuring rspamd per these pages and ever seeing it actually apply a DKIM signature to an outgoing email. Elsewhere on the web, one can find many unanswered questions about rspamd not adding a DKIM signature. After enabling debug for this via creating a file /etc/rspamd/local.d/logging.inc containing merely:
    – – –
    debug_modules = [“dkim_signing”]
    – – –
    root@localhost:~# service rspamd reload
    root@localhost:~#

    one may read on:
    https://rspamd.com/doc/faq.html#how-to-debug-some-module-in-rspamd
    “Please bear in mind that some modules do not produce any useful debug so far.”

    Before learning enough about this software to supply some useful debugging code, I started seeking insights from:
    root@localhost:~# rspamadm configdump
    and looking at the contents of /etc/rspamd/rspamd.conf . One will eventually notice (at least in rspamd 2.7-1 as distributed by Debian) an unusual value:

    root@localhost:~# rspamadm configdump
    dkim_signing {
    :
    sign_networks [
    “127.2.4.7”,
    ]
    and eventually find in the file /etc/rspamd/cgp.inc the source of this strange value:
    – – –
    dkim_signing {
    sign_networks = [127.2.4.7];
    }

    options {
    local_addrs = [127.2.4.7];
    }
    – – –

    One can (partially) override this via creating a new file:
    /etc/rspamd/local.d/dkim_signing.conf
    – – – – – –
    sign_networks = [your.IPaddr.from.ISP];

    options {
    local_addrs = [127.0.0.1];
    }
    – – – – – –
    The local_addrs value is the important one in the context of this series of instructions, while the sign_networks value will be important if you wish to have DKIM-signed email which originates from a system you use to configure and maintain via remote access to the mail server system @ 127.0.0.1 .

    root@localhost:~# rspamadm configtest
    syntax OK
    root@localhost:~# systemctl restart rspamd
    root@localhost:~# rspamadm configdump dkim_signing
    *** Section dkim_signing ***
    use_esld = true;
    symbol = “DKIM_SIGNED”;
    allow_envfrom_empty = true;
    sign_networks [
    “your.IPaddr.from.ISP”,
    “127.2.4.1”, ## why ???????????????????
    ]
    sign_authenticated = true;
    allow_username_mismatch = true;
    sign_local = true;
    allow_hdrfrom_mismatch = false;
    selector = “dkim”;
    try_fallback = true;
    path = “/var/lib/rspamd/dkim/$domain.$selector.key”;
    use_redis = false;
    options {
    local_addrs [
    “127.0.0.1”,
    ]
    }
    selector_map = “/etc/rspamd/dkim_selectors.map”;
    key_prefix = “DKIM_KEYS”;
    use_domain = “header”;
    allow_hdrfrom_multiple = false;

    *** End of section dkim_signing ***
    root@localhost:~#

    and maybe then you also will find:
    Authentication-Results: … dkim=pass …
    as a header in your emails, too.

    1. Christoph Haas

      I don’t know why rspamd’s author chose those IP addresses. Maybe it was just meant as an example. I assume that “sign_local” is what triggers adding the DKIM signature.

      This is really all I had to set:

      /etc/rspamd/local.d# cat dkim_signing.conf :
      selector_map = “/etc/rspamd/dkim_selectors.map”;

      Then add the selector in the /etc/rspamd/dkim_selectors.map file and put the certificate in place.

      I usually found configuration errors in the log file:

      grep dkim_signing /var/log/rspamd/rspamd.log

      1. > I don’t know why rspamd’s author chose those IP addresses. Maybe it was just meant as an example.

        I gather those IP addresses are used by CommuniGate Pro MTA, per the comments in /etc/rspamd/cgp.inc:
        https://www.communigate.ru/
        and appear to have been distributed for the last three years since rspamd 2.6 (at least via Debian).
        It is irritating or worse that while your choice in /etc/rspamd/local.d/dkim_signing.conf does override the default value for local_addrs, your choice there does not displace the default value for sign_networks. One is left with the choice of also editing /etc/rspamd/cgp.inc and remembering to repeat after any update of rspamd, or convincing oneself (in a shared hosting environment) that no one running CommuniGate Pro MTA might appear in your system at 127.2.4.1 (since 127.x.x.x should never be on any network; and thus be able to send DKIM authenticated email from “your” domain).

        dkim_signing; lua_dkim_tools.lua:170: mail is ineligible for signing
        does appear in the log file until the configuration is corrected … but that does not provide much diagnostic guidance.

  11. Hi Christoph,
    thanks for your great guide on running your own mailserver. I am following your tutorials since forever an like the update on new features over the time, like DKIM and SPF.
    I am allowing people to forward their mail to their own mailserver/mailadress – so they use my domain with a nice address. In the last time it often happens that the forwarded message bounces due to SPF fails?! I just learned about SRS – https://de.wikipedia.org/wiki/Sender_Rewriting_Scheme – maybe you can give a hint if this could help and how to implement in your guide?

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to Top