[2019-11-22]

Optional: Server-based mailbox encryption

Guest column by Eelke 🙂

Warning

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.

Introduction

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.

Advice

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

Important

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:

/etc/dovecot/dovecot-sql.conf.ext

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

Original:
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=127.0.0.1 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';

New:

driver = mysql
connect = host=127.0.0.1 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
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

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
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

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 ,
       email,
       crypt 
)
VALUES (
       '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

Information

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.

20 thoughts on “Optional: Server-based mailbox encryption

  • 2020-01-19 at 14:38
    Permalink

    Are the sql queries in “/etc/dovecot/conf.d/auth-sql.conf.ext” still valid?
    The “original”:
    driver = mysql
    connect = host=127.0.0.1
    dbname=mailserver
    user=mailuser
    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?

    Thanks

    Reply
    • 2020-01-23 at 13:56
      Permalink

      you are correct, they should both be the same

      Reply
  • 2020-02-02 at 22:19
    Permalink

    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

    Reply
    • 2020-02-07 at 12:39
      Permalink

      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.

      Reply
      • 2020-09-10 at 11:06
        Permalink

        Hi Eelke,
        I’m facing an error I can’t figure out. I think I’ve set up everything correctly, I can generate the key and I get the Public ID string from the command. I have confirmed in the mysql database that the user has encyption enabled (set to 2).I can receive emails and I can read the subject but the body of the email is empty (just a blanc screen).
        On the log I see the error: Mailbox INBOX: UID=2: read() failed: read(/var/vmail/example.org/john/Maildir/cur/1599724124.M941006P8473.mx,S=8849,W=9054:2,) failed: Decryption error: no private key available (read reason=)
        I have rebuilt the database, deleted the domain directories in the /var/vmail and created them again through mysql queries, reviewed all the configuration files – everything seems to be configured correctly. Can you assist?

        Reply
  • 2020-03-05 at 00:40
    Permalink

    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):
    https://blog.onefellow.com/post/167267172603/server-side-email-encryption-with-dovecot

    This here describes what we are doing in this guide:

    https://dovecot.markmail.org/message/kqd3rlryfbwfo6fi

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

    Reply
    • 2020-03-06 at 13:38
      Permalink

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

      Reply
    • 2020-03-06 at 13:51
      Permalink

      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

      Reply
  • 2020-03-07 at 13:23
    Permalink

    @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.

    Reply
    • 2020-03-15 at 10:43
      Permalink

      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?

      Reply
      • 2020-05-06 at 17:06
        Permalink

        Hi,

        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.

        Reply
  • 2020-03-15 at 23:54
    Permalink

    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:

    https://www.youtube.com/watch?v=QCKNUm336R4

    Reply
  • 2020-07-29 at 20:45
    Permalink

    The following code snippets from above do not mention the “quota” field. Should they?

    “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
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;”

    “REPLACE INTO mailserver.virtual_users (
    id ,
    domain_id ,
    password ,
    email,
    crypt
    )
    VALUES (
    ‘1’, ‘1’, ‘{BLF-CRYPT}$2y$05$.WedBCNZiwxY1CG3aleIleu6lYjup2CIg0BP4M4YCZsO204Czz07W’, ‘john@example.org’, ‘2’
    );”

    I ask as I had a problem logging in to roundcube only after I attempted to install this encryption capability. I ended up deleting the table and recreating with the code above. I then used the below code to alter the table (as shown on the migration page of the tutorial)
    “alter table virtual_users add column quota int(11) not null default 0;”

    After this, I can now log into roundcube. This leads me to the second problem:

    Issue #2 – I only have the Inbox folder showing in roundcube. I was expecting to see the Sent, Trash, and Junk folders too. Did I miss something somewhere? I went through the roundcube settings and could not find where to toggle them to show. Do they need to be created within roundcube? I have not used imap and roundcube much.

    This is the best email build tutorial that I have found on the internet. Thank you!

    Reply
    • 2020-08-02 at 07:27
      Permalink

      >I only have the Inbox folder showing in roundcube. I was expecting to see the Sent, Trash, and Junk folders too. Did I miss something somewhere?

      You didn’t miss anything, it’s this guide missing a few things here and there.
      In /etc/dovecot/conf.d/15-mailboxes.conf, add “auto = subscribe” to the folders you want. For example:

      mailbox Drafts {
      special_use = \Drafts
      auto = subscribe
      }

      Also, comment out the ‘mailbox “Sent Messages”‘ block, as having two \Sent mailboxes is confusing.

      Also, somewhere in this guide or its comments it says that you can add “autoexpunge = 30d” to mailboxes, e.g. Trash and Junk, to have those emails deleted after 30 days.

      Reply
    • 2020-08-03 at 13:49
      Permalink

      correct, the quota part is missing here, but it should be in there to match the rest of the guide

      Reply
  • 2020-09-09 at 02:03
    Permalink

    Very nice. Couple of comments & a question.

    After installing /etc/dovecot/conf.d/10-mailcrypt.conf and updating the sql query in /etc/dovecot/dovecot-sql.conf.ext i had to restart dovecot before any of the doveadm mailbox cryptokey commands would work.

    Also, i had already set up a use & mailbox, then deleted all of the email so it was empty (so it is a case of existing user and mailbox), but the command: doveadm mailbox cryptokey password -u john2@example.org -n newpass -o “yes” doesn’t work… i first had to generate crypto with your first command – doveadm -o plugin/mail_crypt_private_password=password mailbox cryptokey generate -u john@example.org -U.

    then it all works fine.

    as i was playing around with it, i was trying to figure out a way to automate the updating of the cryptokey password.. what i found was there is a way to force creation of a new key:

    doveadm -o plugin/mail_crypt_private_password=password mailbox cryptokey generate -u -f john@example.org -RU. this evidently creates new user keypair and re-encrypts and create folder keys.

    to automate, i was thinking of letting the user use a GUI scheme to update their password as they might ordinarily do, detect that the password was changed, then extract it using mysql from the shell and execute the above command using the hashed value:

    doveadm -o plugin/mail_crypt_private_password={dovecot encrypt scheme}password-hash-from-mysql mailbox cryptokey generate -u -f john@example.org -RU.

    One downside to this seems to be that you are left with multiple (inactive) keys – so each time you force the creation of a new key, the old ones remain, but this would eliminate the need for the old password

    Do you think that use of the combination {Encrypt-Scheme}HASHED-PW will work?

    Reply
    • 2020-09-11 at 04:09
      Permalink

      answer to my question about using {Encrypt-Scheme}HASHED-PW is no… has to be the plain text password.

      Reply
  • 2020-09-10 at 22:24
    Permalink

    Ok, I got it to work at last… it took some trial and error and I think others with the same poor sysadmin skills as myself may fall on the same traps as I did so a few things I would note:

    1. You will not be able to use ISPMail Admin or GRSoft Virtual Mail Manager to create the user accounts, you will have to do it manually through mysql commands because you need to provide the new crypt value.

    2. To edit the mysql table to include the crypt column use (in mysql):
    MariaDB [(none)]> USE mailserver;
    ALTER TABLE `virtual_users
    ADD COLUMN `crypt` int(11) NOT NULL
    AFTER password;
    FLUSH PRIVILEGES;

    3. To create a new user with encryption enabled go to adminer (or use mysql shell) and find out the id of the domain you will be using for the account (in Adminer go to virtual_domains > select data); then generate the hash for the password you will use for the account with the command:
    doveadm pw -s BLF-CRYPT
    Make a note of the hash and include it in the mysql command to create the user:
    INSERT INTO virtual_users (domain_id, email, password, crypt) VALUES ('4', 'john@example.org', '{BLF-CRYPT}$2y$05$.WedBCNZiwxY1CG3aleIleu6lYjup2CIg0BP4M4YCZsO204Czz07W', '2');

    3. In the /etc/dovecot/dovecot-sql.conf.ext listed above there is a missing line at the end:
    iterate_query = SELECT email AS user FROM virtual_users
    This should be present. Alternatively you can also use the following instead of the last 2 lines:
    password_query = SELECT email as user, password, '%w' AS userdb_mail_crypt_private_password FROM virtual_users WHERE email='%u'

    4. Finally, where I really struggled to figure it out – to create the user key (btw, the two commands listed above for a user with or without mailbox are the same) where it says =password is your account’s plaintext password (the one used before when running the doveadm pw -s BLF-CRYPT) So it should be
    doveadm -o plugin/mail_crypt_private_password=YOUR_PASSWORD mailbox cryptokey generate -u john@example.org -U

    Hope it saves others some of the time it took me to figure it out.

    Thank you, Christoph, for all the work and time you punt into these guides, I’m on the 3rd interaction (since jessie) and every time I add something new. I’ve also configured Horde on the server and works great.

    Reply
    • 2020-10-12 at 18:38
      Permalink

      Very helpful, thank you. I had to run the doveadm key generation command as a user with sufficient write permissions

      Reply

Leave a Reply

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