When I am in the field and I talk to email administrators most of them know what SPF is, but when we are talking about DKIM ( Domain Keys Identified Mail ) it seems that only the bigger company’s have implemented this protocol. Most of the time it was not the companies choice to implement DKIM but it came from a requirement enforced by a regulator. From a security perspective it would be a whole lot better if companies started to implement DKIM as this is a simple and easy to implement protocol with little to no overhead. The biggest benefit of DKIM for a mail receiver and sender comes from the ability to prove that the mail and parts of the header have not been tampered with between 2 mail servers. So as long as the sender is who he claims he is ( Authorized by SPF & DKIM ) and the mail it self has not been tampered with ( DKIM ) a mail flow starts to become more secure.
This is part 3 of a multi part post. You can find the other parts here:
Antispam counter measures explained Part 1: How the Sender Policy Framework really works
Antispam counter measures explained Part 2: Advanced SPF records, Best practice and biggest mistakes
Antispam counter measures explained Part 3: How DKIM really works and best practice
So what is DKIM exactly
Following the specification:
DomainKeys Identified Mail (DKIM) is an email authentication method designed to detect email spoofing. It allows the receiver to check that an email claimed to have come from a specific domain was indeed authorized by the owner of that domain. It is intended to prevent forged sender addresses in emails, a technique often used in phishing and email spam. In technical terms, DKIM lets a domain associate its name with an email message by affixing a digital signature to it. Verification is carried out using the signer’s public key published in the DNS. A valid signature guarantees that some parts of the email (possibly including attachments) have not been modified since the signature was affixed. Usually, DKIM signatures are not visible to end-users, and are affixed or verified by the infrastructure rather than message’s authors and recipients. In that aspect, DKIM differs from end-to-end digital signatures like SMIME or PGP. Verifying the signature asserts that the hashed content has not changed since it was signed and asserts nothing else about “protecting” the end-to-end integrity of the message. A valid DKIM signature only means that the email message you got was the same message the sender signed.
In Plain text:
By itself, a DKIM signature:
- Does not authenticate or verify the contents of the message header or body, such as the author From field, beyond certifying data integrity between the time of signing and the time of verifying.
- Does not offer any assertions about the behaviors of the signer.
- Does not prescribe any specific actions for receivers to take upon successful signature verification.
- Does not provide protection after signature verification.
- Does not protect against re-sending (replay of) a message that already has a verified signature; therefore, a transit intermediary or a recipient can re-post the message — that is, post it as a new message — with the original signature remaining verifiable, even though the new recipient(s) might be different from those who were originally specified by the author.
Yes you are reading that correctly. Basically with no other mechanism DKIM alone doesn`t mean anything more than that the message in transit or at least part of it has not changed and it came from the signing sender. And Yes this can even be a phishing sender. That is why you should always bundle DKIM with other protocols like SPF.
If we take a look at the popularity of the protocol we see that DKIM is not as wide spread as the millions of domains using SPF but the support took a big jump in 2016 when DMARC became more popular. at the moment a rough estimate of 40K domains use DKIM. This number might grow fast again in the near feature as platforms like O365 now have full support for DKIM.
Let`s take a deeper dive and see how it works to get a better understanding of how it works.
So how does it work
Sender Actions
DKIM is designed to work with limited administrative overhead. The basics are that every sender hashes header and body information for every mail it sends out. The receiving party can do a hash validation of the message and if the hashes are the same, the message is authentic and the sender has proven that he is authorized by the domains owner of the domain in the “d=tag”. Now this would not be very secure if the hashes would be in plain text in the email. To mitigate this DKIM uses RSA key exchanges to secure the hash transfers and uses DNS to distribute the public keys.
Lets take a look at the sending sever and the steps it will go through:
- The body part of the email is being hashed using SHA256
- The hash will than be signed using a the private key of a RSA Algorithm. ( This can be any signing certificate with strength of 1024 or 2048 bit)
- The hash will be encoded using the Base64 encoding algorithm and stored in the “bh” tag
- All the header fields specified in the H tag of the DKIM signature will be hashed in sequence
- If fieldnames are duplicated they will be processed in order from down to top.
- A non-existing field will be added as a empty string, so that adding a field with that name will break the signature.
- The second hash will be encoded using base 64 encoding algorithm and stored in the “b” tag.
- All the other DKIM tag are created with the data used and the DKIM signature is being inserted in the messages.
- The message will continue to the next process in the transport pipeline.
The DKIM signature will look something like this:
DKIM-Signature: v=1; a=rsa-sha256; d=Tech-savvy.nl; s=savvy2017;
c=relaxed/simple; q=dns/txt; l=1234; t=1117574938; x=1118006938;
h=from:to:subject:date:keywords:keywords;
bh=CdLXdJOc9G2OllNKCdLXdJOifdAlpomD7KCdLXdJ
b=kceqwvjifdAlpomD7+ffswpbR43lc0dssvGDBJjDeOOllNHbhfddG75
So the message is now signed by DKIM and send over to the recipients mail server or the next hop in line. There is one more thing that the sender must have done before he has send out the email. The sender must publish the public key of the RSA set that he used to sign the message and distribute it to DNS.
Publish your public keys in DNS
As part of the DKIM solution a sender has to publish his public keys into the DNS zone that he used when signing the message. a receiver will look at the “d=”,”s=” and “q=” tags to determine where the DKIM public key can be retrieved. So as a sender you have to make sure that you publish the public key at that location and that the DNS infrastructure can handle the additional DNS request that will be coming in from DKIM.
If we look as the sample DKIM signature above the public key would be stored in a TXT record named “savvy207._domainkey.tech-savvy.nl”. The way this is constructed is “Selector + _domainkey + domain” or “S=tag + _domainkey + d=tag”. A receiver will also look at the “q=” tag due to the options of using DNS RR typed records that where in earlier (draft) versions of the DKIM standard. DNS RR (type 99) records are deprecated sinds 2014 by RFC 7208 and should not longer be used. This leaves DKIM with the only valid value of “q=dns/txt” as valid public key distribution. So for each certificate you use for DKIM signing you need a separate selector to publish the private key.
Selectors
To support multiple concurrent public keys per signing domain, the key namespace is subdivided using “selectors”. Selectors can have any name convention you want. Some examples might be platform names (“IronPort”,”Barracuda” etc. ), the signing date (“january2017”, “february2017” etc. ) or the party using that selector (“HRMsystem”,”3rdparty1″).
Selectors are needed to support some important use cases. For example:
- Domains that want to delegate signing capability for a specific address for a given duration to a partner, such as an advertising provider or other outsourced function.
- Certificate roll over when your certificate has expired.
Periods are allowed in selectors and are component separators. When keys are retrieved from the DNS, periods in selectors define DNS label boundaries in a manner similar to the conventional use in domain names. Selector components might be used to combine dates with locations, for example, “march2005.reykjavik”. In a DNS implementation, this can be used to allow delegation of a portion of the selector namespace.
- Reusing a selector with a new key makes it impossible to tell the difference between a message send with the old key or a message send with the new key. Due to this it is best to not reuse selectors when renewing a certificate. If you do reuse the selector you can expect issues.
Receiver Actions
A receiver should always try to verify a DKIM signature as soon as possible after the message is received. It is not advised to wait until the mail reached the user. Most likely you will verify the DKIM signature on your edge MTA’s (Mail transfer Agent). This is due to possible alterations of the email message and time that the signatures are valid.
As soon as the edge MTA receives the email it should follow the following flow:
- Extract Signatures from the Message. The order in which verifiers try DKIM-Signature header fields is not defined; verifiers MAY try signatures in any order they like.When a signature successfully verifies, a verifier will either stop processing or attempt to verify any other signatures, at the discretion of the implementation. A verifier MAY limit the number of signatures it tries to avoid denial-of-service attacks.
- Validate the Signature Header Field. Implementers MUST carefully validate the format and values in the DKIM-Signature header field. Any inconsistency or unexpected values MUST cause the header field to be completely ignored and the verifier to return PERMFAIL.If any tag listed as “required” in Section 3.5 is omitted from the DKIM-Signature header field, the verifier MUST ignore the DKIM-Signature header field and return PERMFAIL
- Retrieve the public key as described in the algorithm in the “q=” tag
- Query for the public key using ( s=tag + _domainkey. + d=tag -> selector._domainkey.domain).
- If the query for the public key fails times out the verifier MAY defer acceptance of this email and return TEMPFAIL (key unavailable). If verification is occurring during the incoming SMTP session, this MAY be achieved with a 451/4.7.5 SMTP reply code.
- If the query for the public key fails because the corresponding key record does not exist, the verifier MUST return PERMFAIL.
- If the query for the public key returns multiple key records, the verifier may choose one of the key records or may cycle through the key records. ( This is not recommended and conflicts in the DKIM RFC’s.).
- If the result returned from the query is not a correct TXT record the verifier MUST ignore the key record and return PERMFAIL.
- If the public key data (the “p=” tag) is empty, then this key has been revoked and the verifier MUST treat this as a failed signature check and return PERMFAIL (key revoked).
- If the “h=” tag exists in the public key record and the value is not in the “a=” tag in the DKIM-Signature header field, the verifier MUST ignore the key record and return PERMFAIL.
- If the “g=” tag in the public key does not match the Local-part of the “i=” tag in the message signature header field, the verifier MUST ignore the key record and return PERMFAIL.
- Verify the header and body hash using the public key
- Prepare the data to be hashed based on the algorithm defined in the “c=” tag, the body length specified in the “l=” tag, and the header field names in the “h=” tag.
- Based on the algorithm indicated in the “a=” tag, compute the message body hash.
- Verify that the hash of the canonicalized message body computed in the previous step matches the hash value in the “bh=” tag.If the hash does not match, the verifier SHOULD ignore the signature and return PERMFAIL.
- Based on the algorithm indicated in the “a=” tag, compute the message header hash.
- Verify that the hash of the message headers computed in the previous step matches the hash value in the “b=” tag.If the hash does not match, the verifier SHOULD ignore the signature and return PERMFAIL.
- The message is verified. At this moment additional header data may be injected about the outcome of the DKIM record.
- The message can be sent to the next hop.
When a email has been verified a MTA can inject additional header information about the outcome of the DKIM validation. This way any MTA along the way can use the header and does not need to verify the DKIM header again.
DKIM tags
By now you should be familier with the term tags or DKIM tags and have a understanding of what they do and what they are used for. We know of 2 types of tag in the DKIM standard. The tags used in the DKIM header and the tags used in the DKIM public key record. The following tables explain in detail what you can configure with the tags or where they are being used for.
DKIM Tags in the DKIM header
Tag name | Required Optional Recommended |
Tag Description |
---|---|---|
v | Required | This is the version information used. At the time of this writing only v=1 is in use. Note that verifiers must do a string comparison on this value |
a | Required | This is the signing algorithm used. Receivers MUST support “rsa-sha1” and “rsa-sha256”. For senders it is advised to use “rsa-sha256”. |
d | Required | The domain of the sender. This is the domain that will be queried for the public key. This domain MUST be the same as or a parent domain of the “i=” tag , or it MUST meet the requirements for parent domain signing. When presented with a signature that does not meet these requirement, receivers MUST consider the signature invalid. |
s | Required | This is the selector. The selector is used to indicate a certificate within a domain effectively creating the possibility to use multiple certificates for the same domain. |
c | Optional default is “simple/simple” |
This tag informs the verifier of the type of canonicalization used to prepare the message for signing. It consists of two names separated by a “slash”. If only one algorithm is named, that algorithm is used for the header and “simple” is used for the body |
q | Optional default is “dns/txt” |
This is the query mode to retrief the public key. Each query method is of the form “type[/options]”. At the time of this writing only the “DNS/TXT” option is valid |
i | default is an empty Local-part followed by an “@” followed by the domain from the “d=” tag |
Identity of the user or agent (e.g., a mailing list manager) on behalf of which this message is signed |
l | Optional default is entire body |
This is the length ( in number of octets) of the canonicalized part of the body that has been signed. This value MUST NOT be larger than the actual number of octets in the canonicalized message body. |
t | Recommended default is an unknown creation time |
The time that this signature was created. The format is the number of seconds since 00:00:00 on January 1, 1970 in the UTC time zone. The value is expressed as an unsigned integer in decimal ASCII.To avoid denial-of-service attacks, implementations MAY consider any value longer than 12 digits to be infinite. |
x | Recommended default is no expiration |
This is the expiration time of the signature. The format is the same as in the “t=” tag, represented as an absolute date, not as a time delta from the signing timestamp. The value is expressed as an unsigned integer in decimal ASCII, with the same constraints on the value in the “t=” tag. Signatures MAY be considered invalid if the verification time at the verifier is past the expiration date. The verification time should be the time that the message was first received |
h | Required | The header fields that are signed in the DKIM signature. The list must be a colon-separated list of header field names. The field MUST contain the complete list of header fields in the order presented to the signing algorithm. The field MAY contain names of header fields that do not exist when signed; nonexistent header fields do not contribute to the signature computation |
b | Required | The header signature data as base64 encoded string. Whitespace is ignored in this value and MUST be ignored when reassembling the original signature. |
bh | Required | The body signature data as base 64 encoded string. The hash in limited to the length indicated by the “l=tag”. Whitespace is ignored in this value and MUST be ignored when reassembling the original signature |
z | Optional default is null |
Copied header fields. A vertical-bar-separated list of selected header fields present when the message was signed, including both the field name and value. It is not required to include all header fields present at the time of signing |
DKIM Tags in the DKIM public key record
Tag name | Required Optional Recommended |
Tag Description |
---|---|---|
v | Recommended default is “DKIM1” |
Version of the DKIM key record. If specified, this tag MUST be set to “DKIM1”. This tag MUST be the first tag in the record. Records beginning with a “v=” tag with any other value MUST be discarded |
g | Optional default is “*” |
Granularity of the key. This value MUST match the Local-part of the “i=” tag of the DKIM-Signature header field (or its default value of the empty string if “i=” is not specified), with a single, optional “*” character matching a sequence of zero or more arbitrary characters (“wildcarding”). An email with a signing address that does not match the value of this tag constitutes a failed verification. The intent of this tag is to constrain which signing address can legitimately use this selector, for example, when delegating a key to a third party that should only be used for special purposes. Wildcarding allows matching for addresses such as “user+*” or “*-offer”. An empty “g=” value never matches any addresses. |
h | Required | The domain of the sender. This is the domain that will be queried for the public key. This domain MUST be the same as or a parent domain of the “i=” tag , or it MUST meet the requirements for parent domain signing. When presented with a signature that does not meet these requirement, receivers MUST consider the signature invalid. |
s | Optional defaults to allowing all algorithms |
Acceptable hash algorithms. A colon-separated list of hash algorithms that might be used. Signers and Verifiers MUST support the “sha256” hash algorithm. Verifiers MUST also support the “sha1” hash algorithm. |
k | Optional default is “rsa” |
Key type. Signers and verifiers MUST support the “rsa” key type |
n | Optional default is empty |
Notes that might be of interest to a human. This can be a change request number when DKIM got implemented or any other text string. |
p | Required | Public-key data in base64 form. An empty value means that this public key has been revoked |
s | Optional default is “*” |
Service Type. A colon-separated list of service types to which this record applies. Currently defined service types are as follows: “*” means all, “email” means electronic mail. This switch should not be used due to the poor adaptation rate. |
t | Optional default is no flags set |
Flags, represented as a colon-separated list of names. The defined flags are as follows: “y” means this domain is testing DKIM. Verifiers MUST NOT treat messages from signers in testing mode differently from unsigned email, even if the signature fails to verify. Verifiers MAY wish to track testing mode results to assist the signer. “s” means any DKIM-Signature header fields using the “i=” tag MUST have the same domain value on the right-hand side of the “@” in the “i=” tag and the value of the “d=” tag. That is, the “i=” domain MUST NOT be a subdomain of “d=”. Use of this flag is RECOMMENDED unless subdomaining is required. |
Creating the DKIM DNS records
To create you DNS public record you can craft the record yourself or you can use a web based wizard. There are lot’s of great wizards out there here are some of the once I often use:
- Easy and simple certificate and record creation wizard. http://dkimcore.org/tools/keys.html
- Easy and simple certificate and record creation wizard with option to use 1024 or 2048 bit certificates. https://www.socketlabs.com/domainkey-dkim-generation-wizard/
- Advanced wizard with support for almost all tags. https://www.dnswatch.info/dkim/create-dns-record
A typical DKIM TXT record should look something like This:
“v=DKIM1; n=changerequest1234; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8xxS8INnzDDf2mJa/i3iKT4a6sKGDlUVk6EFwLtGzkoERg7nqYUB8LPEYzYrNIqlNm72DsP0Vsnh+dFOJCM43nEUf0UZHOQAUKGsYQXrbEW1FeQwujG/hf8J5+WSTdKaq8FPqDDuvfBy9FSjqWmlLZkCMqk7BZFt2XNrwbJnydwIDAQAB; t=s”
This record should be published in the DNS zone: “feb2017site._domainkey.tech-savvy.nl” as a TXT record.
Best practices and known issues
Best practice:
- Use certificates with a key strength of 1024 or 2048 bit. smaller keys are subjected to security risks.
- keep the selectors unique and don’t reuse them. A certificate rollover means you create a new selector. Using a part of the date in the selector name makes it easy to identify for example the date the certificate will expire ( example: mycert052018 ). Now you can query DNS for expiration date instead of investigating the certificates themselves.
- Separate your MTA’s selectors from 3rd party servers using different certificates and selectors. This way YOU stay in control of what a 3rd party signs on your behalf.
- Use DNS SEC to harden your DNS system that is publishing the public keys.
- DKIM is better considered to be a transport security mechanism than a anti phishing mechanism if used solo. If you want better anti phishing combine it with SPF and DMARC.
Known issues:
- Do not use 4096 bit certificates. the size of the key does not fit in a 512-byte DNS UDP response packer.
- DKIM does not enforce a policy nor sends out an advise to a receiving party ( unlike SPF or DMARC ).
- Spammers can sign email with DKIM using there own domains in the “d=” tag that differs from the “mail from:” header and still validate DKIM.
- No check or relation between the header “Mailfrom:” and DKIM. ( This is the biggest weakness of DKIM as a anti phishing prevention mechanism)
Attack vectors:
- In the case of forgeries on a large scale, DNS servers could see a substantial increase in queries resulting in a DOS attack against the DNS servers.
- An attacker could send messages with large numbers of faulty signatures, each of which would require a DNS lookup and corresponding CPU time to verify the message. This could be an attack on the domain that receives the message.
- In this attack, a spammer sends a message to be spammed to an accomplice, which results in the message being signed by the originating MTA. The accomplice resends the message, including the original signature, to a large number of recipients, possibly by sending the message to many compromised machines that act as MTAs. The messages, not having been modified by the accomplice, have valid signatures.
- In some cases, it may be possible to extract private keys using a remote timing attack [BONEH03]. Implementations should consider obfuscating the timing to prevent such attacks.
- An attacker could create a large RSA signing key with a small exponent, thus requiring that the verification key have a large exponent. This will force verifiers to use considerable computing resources to verify the signature.
So this is the end of part 3 and kinda everything you should know about DKIM. Again if you want to know even more its best to read the RFC’s.
Till next time
Martijn van Geffen
Thanks Martijn, that was a great read.