Optional: Server-based mailbox encryption

Guest column by Eelke 🙂


This chapter is for people who know what they are doing and are aware of the fact that improperly handling can cause email messages being lost.
Also, the features used in this chapter are experimental as stated in the Dovecot documentation.


Now we have come so far, that we have a new and shiny fully functional email server. With security settings that fits our needs and are up to date for the years to come. We can now securely send and receive email mostly (preferred) via a TLS connection. So although the email messages are in plain text, they are send over the wire in an encrypted form.

But when hosting your own mail server needs that little extra finishing touch security wise, we can go a step further. We can in fact, store our email at rest in a encrypted form. So when the server is compromised, all email messages are unreadable for the attacker.

Here’s where a plugin comes into play called “mail-crypt“. Its a plugin for Dovecot. The mail-crypt plugin is used to secure email messages stored (at rest) on the disc. Email messages are encrypted before written to disc. This is completely transparent to the user using the mail server.

The plugin has several options for encrypting messages:

  • There can be a single encryption key for the whole system (less secure)

    We are not going to take this route, as the keys are stored on the server. When the server get compromised, the keys can be used to decrypt the email messages.
  • Each user has a key of his/her own. The used cryptographical methods are widely used standards and keys are stored in portable formats, when possible.

    We are going to use this route, although this is an experimental option as stated in the dovecote documentation.

Functional Overview

The use of the mail-crypt plugin depends on a user having: a keypair (a private and a public key for asymmetric cryptography). These keys are provisioned in a variable via the user database or directly from Dovecot configuration files.

The public half of the provisioned keypairs are used to generate and encrypt keys for symmetric encryption. The symmetric keys are used to encrypt and decrypt individual files. Symmetric encryption is faster and more suitable for block mode storage encryption.

The symmetric key used to encrypt a file is stored, after being encrypted with the public asymmetric key, together with the file.

Encryption Technologies

The mail-crypt plugin provides encryption at rest for emails. Encryption of the messages is performed using the symmetric Advanced Encryption Standard (AES) algorithm in Galois/Counter Mode (GCM) with 256 bit keys. Integrity of the data is ensured using Authenticated Encryption with Associated Data (AEAD) with SHA256 hashing.

The encryption keys for the symmetric encryption are randomly generated. These keys in turn are encrypted using a key derived with from the provisioned private key. Provisioned private keys can be Elliptic Curve (EC) keys or RSA Encryption is done using the Integrated Encryption Scheme (IES). This algorithm is usable both with EC and RSA keys.

Lets go

We first need to tell dovecot that we are going to use mail-crypt, so take the following step. To make Dovecot use mail-crypt plugin we need to create a ‘conf’ file (configuration file).

Start by creating a file called /etc/dovecot/conf.d/10-mailcrypt.conf that contains:

mail_attribute_dict = file:%h/Maildir/dovecot-attributes

mail_plugins = $mail_plugins mail_crypt

plugin {
mail_crypt_curve = secp521r1
mail_crypt_save_version = 2
mail_crypt_require_encrypted_user_key = yes

This file is telling dovecot to use mail-crypt, and is also setting the used encryption curve, the version of the mail-crypt plugin used and the setting to set encryption per user (folder)

For the setting “mail_crypt_save_version = ” We have the option to use “0”, which means: encryption is off for new emails that arrive at the system.

Existing encrypted emails can still be read by the system.
We also have the option “1” but that’s is used as an old version, and should not be used anymore.

So we go for version “2

mail_attribute_dict has to be set since it is used to store the keys.


Any valid curve supported by cryptographic library is supported. If you wish to have EC (elliptic curve) keys, you may find curves using this openssl command:

openssl ecparam -list_curves


it has to be the same crypto used for the password scheme in the file: /etc/dovecot/dovecot-sql.conf.ext

Later on you’ll see that we will actually set the “mail_crypt_save_version” via the database as a “per user” setting. That way we can controll encryption on a per user base.

Next in line is the following file:


Remember, this is a file you created earlier during the guide, but now we are modifying the file for the use of the mail-crypt plugin

It tells Dovecot how to access the MySQL database and where to find the information about email users/accounts. You will find it well documented although all configuration directives are commented out.

Add these lines at the bottom of the file:

driver = mysql 
connect = host= dbname=mailserver user=mailserver password=x893dNj4stkHy1MKQq0USWBaX4ZZdq 
user_query = SELECT email as user, \ 
  concat('*:bytes=', quota) AS quota_rule, \ 
  '/var/vmail/%d/%n' AS home, \ 
  5000 AS uid, 5000 AS gid \ 
  FROM virtual_users WHERE email='%u'; 
password_query = SELECT password FROM virtual_users WHERE email='%u';


driver = mysql
connect = host= dbname=mailserver user=mailserver password=x893dNj4stkHy1MKQq0USWBaX4ZZdq
user_query = SELECT email as user, \
  concat('*:bytes=', quota) AS quota_rule, \
  '/var/vmail/%d/%n' AS home, \
  5000 AS uid, 5000 AS gid \
  FROM virtual_users WHERE email='%u';
password_query = SELECT password, crypt as userdb_mail_crypt_save_version, '%w' AS userdb_mail_crypt_private_password FROM virtual_users WHERE email='%u';

What these lines mean:

  • driver: the kind of database
  • connect: where to find the MySQL database and how to use it (username, password)
  • password_query: an SQL statement that returns the user (=the email address) and the password from the database where “%u” is the login user name (=we use the email address as the user name of an email account).

Whenever Dovecot needs to check an email user’s password if will run the above query and verify the password against the hash in the database. It will now also check if the user has encryption enabled via the following part of the statement:

crypt as userdb_mail_crypt_save_version, ‘%w’ AS userdb_mail_crypt_private_password

If its version “0” encryption is disabled for new arriving emails, if it is version “2” encryption is enabled for new arriving emails.

The important part here is, that we also use the users password for the encryption part. and because we do that, only the user who knows the password, can decrypt the email messages.
Therefor, if the user lost his/her password, all the messages are left encrypted and cannot be restored.

Database Preparation

As we are going to use encryption on a per user base, we need to tell our server which users are enabled with encryption.
We do this by altering the database.

Previously in the guide we have setup this as a database scheme:

CREATE TABLE IF NOT EXISTS `virtual_users` (
  `id` int(11) NOT NULL auto_increment,
  `domain_id` int(11) NOT NULL,
  `email` varchar(100) NOT NULL,
  `password` varchar(150) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email` (`email`),
  FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE

We change that to:

CREATE TABLE IF NOT EXISTS `virtual_users` (
  `id` int(11) NOT NULL auto_increment,
  `domain_id` int(11) NOT NULL,
  `password` varchar(150) NOT NULL,
  `email` varchar(100) NOT NULL,
  `crypt` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email` (`email`),
  FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE

As you can see we added one line:

`crypt` int(11) NOT NULL

We use that column to define whether a user has encryption enabled or disabled the used value’s are:

  • 0 – Disabled Encryption
  • 2 – Enabled Encryption

We use this column as a value in the file we created earlier: /etc/dovecot/dovecot-sql.conf.ext

password_query = SELECT email as user, password, crypt as userdb_mail_crypt_save_version, ‘%w’ AS userdb_mail_crypt_private_password FROM virtual_users WHERE email=’%u’;

Now to fill that column, you can use the same test data as previously used, but now with that extra value “crypt”

REPLACE INTO mailserver.virtual_users (
       id ,
       domain_id ,
       password ,
       '1', '1', '{BLF-CRYPT}$2y$05$.WedBCNZiwxY1CG3aleIleu6lYjup2CIg0BP4M4YCZsO204Czz07W', 'john@example.org', '2' 

Notice the value “2” at the end of the line which enables encryption.

User Key Creation

Now we have set up mail-crypt, we need to generate the user keys.
There are two situations in which you can do this, both slightly different.

New user without existing mailbox

Use this command when the mailbox has not been created yet. (e.g. there has not been send an email to the new user, but the user does exists in the user db)

doveadm -o plugin/mail_crypt_private_password=password mailbox cryptokey generate -u john@example.org -U

What these options mean:

  • -o – Dovecot option, needed if you use password protected keys
  • -u – Username or mask to operate on
  • -U – Operate on user keypair only

You can also set a new password when the user needs a new password for their email account with:

doveadm mailbox cryptokey password -u john@example.org -n newpass -o password

Existing user with existing mailbox

Use this command when the mailbox HAS been created. (e.g. there HAS been send an email to the new user)

So, if you did not use this:

doveadm -o plugin/mail_crypt_private_password=password mailbox cryptokey generate -u john@example.org -U

Then use this:

doveadm mailbox cryptokey password -u john2@example.org -n newpass -o "yes"

What these options mean:

  • -u – Username or mask to operate on
  • -n – New password
  • -o – Old password


Here is a possibility that the default password was set to “yes”, That’s why we use that value as the “old” password.
otherwise you can leave out the -o option

For more information on this, check out the Dovecot documentation.

Restart Services

Now to make all the changes active, we need to restart the dovecot service.

systemctl restart dovecot

Testing encrypted email

Now when new email comes in, it should be stored encrypted on the disc. Let’s check if that is indeed the cause.

Send yourself an email and do the following test:

cd /var/vmail/example.org/john/Maildir/new/

There should be a file that represents the new email.
Now cat the new email file in this folder:

cat "file-name"

The output should be some unreadable characters.
While normally the email is just plain text and readable.

Success is in the air.

Handle user creation and password changes

As you might understand, email encryption is very nice. And because we use the user’s password for the encryption key’s. The only person who can decrypt and read the messages, is the one with the password.

That said. If the user’s password is lost, ALL the user’s messages are left unreadable, and cannot be recovered.

I repeat: THEY CANNOT be recovered.

The encryption solution is for people who like to be more in control, and have a need for stored encrypted mail. Like for example, hosting a mail server in the cloud without actual ownership of the server.

So password management is a big thing with encryption enabled. A user cannot simple do a password change on their email account like they used to do. (via webmail, or via a web admin tool, e.t.c.)

This is because when the user changes the password of the account, the encryption key password does not change with it. This wil result in unreadable email messages in the mailbox because the key’s password does not match.

As an admin you’ll have to think of a way to deliver a procedure to change a users password. Because there are many way’s to solve this, i’ll only decribe the command to change the encryption key’s password, and let you decide on how to come up with some form of password change-solution

doveadm mailbox cryptokey password -u john2@example.org -n newpass -o "oldpass"

You could ofcourse create a script with some web frontend that does this for the user when they change their password.

And that’s it. You now have saved new email encrypted.

12 thoughts on “Optional: Server-based mailbox encryption

  • 2020-01-19 at 14:38

    Are the sql queries in “/etc/dovecot/conf.d/auth-sql.conf.ext” still valid?
    The “original”:
    driver = mysql
    connect = host=
    password=ChangeMe <- use your database access password instead
    default_pass_scheme = SHA256-CRYPT
    password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';

    Is much different from the one created at earlier (https://workaround.org/ispmail/buster/setting-up-dovecot/). And we are using BLF-CRYPT instead of SHA256-CRYPT right?


    • 2020-01-23 at 13:56

      you are correct, they should both be the same

  • 2020-02-02 at 22:19

    I kept getting an error with Dovecot until I moved `plugin` to the line with the {, as the official Dovecot examples have:
    . . .
    mail_plugins = $mail_plugins mail_crypt
    plugin {
    mail_crypt_curve = SHA256-CRYPT
    . . .

    I’m also getting this and I’m not 100% sure what to do with it. Any ideas?
    Error: mail_crypt_plugin: invalid mail_crypt_curve setting SHA256-CRYPT: error:0D06407A:asn1 encoding routines:a2d_ASN1_OBJECT:first num too large – plugin disabled

    • 2020-02-07 at 12:39

      yes there were some error’s on this page, so i updated them.

      in short:
      dovecot in this guide is using the BLF-CRYPT scheme. (default)

      so the mail-crypt plugin must use the right curve.
      in my example it uses: mail_crypt_curve = secp521r1
      but you are free to choose that yourself.

  • 2020-03-05 at 00:40

    I’ve made it thus far and I am quit happy and prout, to say the least. Thank you very much for this outstanding guide.
    I’ve been reading a lot about the security of mailcrypt/Dovecot and since I am – by far – not an expert, I am posting this question.
    This here basically says that “server side private and public key storage” is not safe (e.g. the VPS provider can decrypt):

    This here describes what we are doing in this guide:


    I would like to have an “expert opinion” on this subject. How good/safe is the mailcrypt solution with user password encrypted keys?

    • 2020-03-06 at 13:38

      I think you could best ask the developers
      As they know all about it

    • 2020-03-06 at 13:51

      As the user is the only one who knows the password on which the key is based. I assume it is pretty save. And also why i did it this way.

      But the dovecot developers might see it differently?

      i find the only downside is key management and password changes.
      as an admin, yes it is doable, but there is no nice way to manage this process. (no simple process/gui/tool)

      and it would be nice to have a backup key that the user has when he/her lost the password.

      so this part of the guide could definitely use some refinement/improvement

  • 2020-03-07 at 13:23

    @Eelke, thanks for your feedback. Helpful. I will try to get in touch with the developers or a forum. I also agree with your last comments about the password change procedure. I think once this would have been established and outlined in this guide we would have something comparable to Tutanota or Protonmail, when it comes to encryption at rest.

    • 2020-03-15 at 10:43

      That looks interesting. I just wonder what database schema is used and if the SQL queries can be adapted. A quick look (without installing it) doesn’t tell anything about it. Have you tried it?

      • 2020-05-06 at 17:06


        First of all, being a follower of your tutorial for a decade, I must say thanks a lot for all your work. I’m looking into things to set up the encryption part as well, though I’d like the keys dictionary stored into the database as well, and looking into Dovecot’s source code, that’s gonna take some work to figure out the tree structure, and how to adapt it to a working SQL schema.
        I’ve looked through the source code of userli, and I think I got the gist of how they’re handling password recovery/update.

        The thing they’ve done, is store the decrypted private key in two libsodium cryptoboxes: one encrypted using the user’s password (similarly to how dovecot handles encryption private keys), the second one encrypted with a “recovery token”, which is a long hexadecimal string, displayed to the user once (they have to store it in a secure location), and not stored into the database.

        So basically, instead of re-encrypting the whole mail directory, only the private key is re-encrypted using the new password, either by decrypting the private key using the user’s password (when the user updates their password knowing the current one), or by using the recovery token.

        I think this can be easily adapted using openssl commands to decrypt and re-encrypt the private key. So basically, you could alter the user table, adding a field to store the recovery-encoded private key, and then re-encrypt the private key using the doveadm command.

        I’ll try to set this all up and tell you how it went, but it may take a few weeks… If I’m successful, I’ll try to come back here and tell you how I’ve been doing things.

  • 2020-03-15 at 23:54

    Have not tried it, this is way above my paygrade…:-)
    But I read the documentation a couple of times. It uses mysql/maria (but also supports others). Altogether this sounds hopeful, doesn’t it? I hope that this makes it into this wonderful guide.

    Here some more info about the (Co)-Author:



Leave a Reply

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