Note 1: Removed code dependancies for Base64 and IO Streams and replaced with newer Java API calls. Thus, the only dependancy is JavaMail.
Note 2: This requires Java 11 and up.
Note 3: This is currently untested.
This is a simple to use library to use DKIM features in conjunction with JavaMail.
This library allows you to
- sign MIME Messages according to the DKIM standard,
- check, whether the DNS resource record for a sending domain is prepared correctly for DKIM.
This library is hosted in the Maven Central Repository. You can use it with the following coordinates:
<dependency>
<groupId>net.markenwerk</groupId>
<artifactId>utils-mail-dkim</artifactId>
<version>1.2.3</version>
</dependency>
Consult the usage description and Javadoc for further information.
The initial version of this library is based on a project called DKIM for JavaMail, which allows to sign MIME Messages according to the DKIM standard and fetch the corresponding DNS resource record. This library extended the DNS resource record check and integrated it in the signing procedure (this is enabled by default, but can be turned off). In addition to retrieving the corresponding DNS resource record for a signing domain and a selector, the check now tests the following, before signing a MIME message:
- Check, whether the retrieved public key fits to the given private key.
- Check, whether the retrieved DKIM granularity fits to the given DKIM identity.
- Check, whether the retrieved DKIM version is
DKIM1
. - Check, whether the retrieved DKIM service type includes
email
.
In order to use DKIM, it is necessary to create a RSA key pair and publish the public key in an appropriate DNS entry.
A RSA private pair with a key size of 1024 bits can be generated as a PEM encoded PKCS8 file like this:
openssl genrsa -out dkim.pem 1024
While DKIM should be compatible with any reasonable key size, it might not be possible to publish arbitrary large public keys. See section 3.3.3. of the RFC for further information on key sizes.
Javas standard API only allows to import PKCS8 files in unencrypted PEM encoding. Therefore, it is either necessary to use a third party library like the Java version of The Legion of the Bouncy Castle or to convert the PEM encoded file into an unencrypted DER encoded file like this:
openssl pkcs8 -topk8 -nocrypt -in dkim.pem -outform der -out dkim.der
The corresponding public key can be obtained from the private key like this:
openssl rsa -in dkim.pem -pubout
This yields an output like this:
writing RSA key
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCf4lvVllV2eoDqxartI0bUiJXD
v+TVhFoGcheKocQyLGrTi8BKamhoDt8yKiecpCm1rZ/nRyxSqIAJFMV3y/XslSVV
2Sc48efPtrdViGUcGYNCC/KrqYNgCF7vRO2oAQ7ePPBohwcR1hzavGeY/AVxpEeI
vixQNmunxkdaqHCLuQIDAQAB
-----END PUBLIC KEY-----
The content of the DNS resource record consists of a set of keys and values, where a typical DNS resource record has values for following keys:
- v: The DKIM version, currently
DKIM1
. - g: The DKIM granularity, used to restrict the allowed sender identities, usualy
*
. - k: The key type, usualy
rsa
. - p: The Base64 encoded public key, usualy a RSA public key.
- s: The allowed service types, usualy
email
or*
. - t: Some flags used by DKIM validators.
See section 3.6.1. of the RFC for further information
To publish such a public key, i.e. for the domain example.com
and the selector foo
, it is necessary to create a DNS resource record with type TXT
for the domain foo._domainkey.example.com
with the following content:
v=DKIM1;g=*;k=rsa;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCf4lvV
llV2eoDqxartI0bUiJXDv+TVhFoGcheKocQyLGrTi8BKamhoDt8yKiecpCm1rZ/n
RyxSqIAJFMV3y/XslSVV2Sc48efPtrdViGUcGYNCC/KrqYNgCF7vRO2oAQ7ePPBo
hwcR1hzavGeY/AVxpEeIvixQNmunxkdaqHCLuQIDAQAB;s=email;t=s
You can use http://dkimcore.org/tools/dkimrecordcheck.html to examine the DNS resource record for a given domain and a given selector and http://dkimvalidator.com/ to verify, that correct DKIM signatures are generated.
We will assume that you already know how to create a SMTP Session
and how create and send a MIME Message with JavaMail, but here is a minimal example how one could send a simple message:
public void sendMail(Session session, String from, String to, String subject, String content) throws Exception {
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.setRecipient(RecipientType.TO, new InternetAddress(to));
message.setSubject(subject);
message.setContent(content, "text/plain; charset=utf-8");
MimeMessage dkimSignedMessage = dkimSignMessage(message, from, "example.com", "foo");
Transport.send(dkimSignedMessage);
}
To sign MimeMessage
with DKIM, you have to configure a DkimSigner
, which can be used multiple times, and create a new DkimMessage
from the original MimeMessage
and the DkimSigner
.
private MimeMessage dkimSignMessage(MimeMessage message, String from, String signingDomain, String selector) throws Exception {
DkimSigner dkimSigner = new DkimSigner(signingDomain, selector, getDkimPrivateKeyFileForSender(from));
dkimSigner.setIdentity(from);
dkimSigner.setHeaderCanonicalization(Canonicalization.SIMPLE);
dkimSigner.setBodyCanonicalization(Canonicalization.RELAXED);
dkimSigner.setSigningAlgorithm(SigningAlgorithm.SHA256_WITH_RSA);
dkimSigner.setLengthParam(true);
dkimSigner.setZParam(false);
return new DkimMessage(message, dkimSigner);
}
When the message is signed, a check is performed to check, whether the DNS resource record for the given domain and the given selector is prepared correctly for DKIM, i.e. if the given identity matches the configured granularity and if the given private key matches the configured public key. A DkimAcceptanceException
is thrown otherwise. Please be aware, that this happens during Transport.send(dkimSignedMessage)
.
To disable this check, which is not recommended, call dkimSigner.setCheckDomainKey(false)
. Using the DomainKeyUtil
, you can perform this check manually like this:
DomainKey domainKey = DomainKeyUtil.getDomainKey(signingDomain, selector);
domainKey.check(from, getDkimPrivateKeyFileForSender(from));