Encrypt specific incoming emails using Dovecot and Sieve

By on .

Encrypt specific incoming emails using Dovecot and Sieve

After reliability, security is perhaps the second most important aspect of a mail server. I was looking for solutions to encrypt all incoming emails (that were not already encrypted) with my PGP key as soon as they arrive on my mail server. This way, even if an attacker gains access to the system, (s)he will not be able to read the contents of emails that are already stored on disk.

There already exists a solution to this; however, this in order to encrypt incoming emails (that are not already encrypted) as soon as they are received, on the server side. However, this guide is for Exim only, and I wanted a solution that worked with my setup (Postfix and Dovecot).

It is possible to achieve using Postfix only. The GPG-Mailgate project does just that. However, it does not appear to be well-maintained, and looking at its issues list makes it look like there are kind a few important outstanding issues (some with patches available, if you wish to tinker with that), so I decided not to use it and instead try to integrate Mike Cardwell's script into Dovecot instead.

Dovecot has a very nice Pigeonhole plugin, which provides Sieve scripts (and client-side management of them) to Dovecot. By integrating PGP encryption at this stage, not only do you not have to edit any Postfix configuration (which by itself is an exercise in patience), and this way every user on the system can now conditionally apply PGP encryption to incoming emails, specifying which encryption key to use for which emails.

The first step was to make Dovecot use the Pigeonhole plugin. This can be done by adding the sieve plugin to the LDA and/or LMTP protocols of Dovecot's configuration file:

protocol lda {
    mail_plugins = $mail_plugins sieve
protocol lmtp {
    mail_plugins = $mail_plugins sieve

Now, we need to get Pigeonhole to use the extprograms plugin, which allows Sieve scripts to call certain (whitelisted, of course) programs. Sadly, this plugin did not have a package on the AUR (a rare occurrence), so I submitted my own.

If you are not using Arch or would rather not use this package, here is how you can build it:

Clone the repository:

$ hg clone http://hg.rename-it.nl/pigeonhole-0.3-sieve-extprograms
$ cd pigeonhole*

Configure it (you may need to adjust the paths to match your installation; the paths below match the default Arch installation of Dovecot and Pigeonhole):

$ ./autogen.sh
$ ./configure --prefix=/usr --with-dovecot=/usr/lib/dovecot --with-pigeonhole=/usr/include/dovecot/sieve --with-moduledir=/usr/lib/dovecot/modules

Build it:

$ make

Install it:

$ sudo make install

Now, Pigeonhole should have the extprograms plugin available. You need to enable it in Dovecot's configuration file:

plugin {
    sieve_plugins = sieve_extprograms

However, the plugin by itself does nothing by default. You need to tell it what it can run, what is allowed, what is not, etc.

There are several ways to do this, as described on the plugin page. The way I went with it is to create a directory called /etc/dovecot/sieve-filters, and then creating symlinks inside that directory, pointing to the real programs that were to be run. This justifies its presence in /etc; it is not a directory full of scripts, but rather a directory indicating the set of scripts that are allowed to run.

Now you need to tell Pigeonhole about it, by adding to the plugin block described earlier:

plugin {
    sieve_plugins = sieve_extprograms
    sieve_extensions = +vnd.dovecot.filter
    sieve_filter_bin_dir = /etc/dovecot/sieve-filters

Notice that +vnd.dovecot.filter was added to the sieve_extensions variable, not sieve_global_extensions as recommended on the extprograms plugin page. This is because we want to allow the user to customize the use of those filters.

Next, you need to grab gpgit and its dependencies:

$ cpan install MIME::Tools
$ cpan install Mail::GnuPG
$ git clone git://perot.me/gpgit # (Clone it in a safe place where you won't delete it accidentally)

And now we need to allow users to use it, by creating a symlink:

$ ln -s /path/to/gpgit/gpgit.pl /etc/dovecot/sieve-filters/gpgit

Now you can restart Dovecot and you should have the ability to use this in a Sieve script:

require ["fileinto", "vnd.dovecot.filter"];

if address :matches "To" "me@domain.com" {
    filter "gpgit" "me@domain.com";
    fileinto "INBOX.encrypted";

This sample script would encrypt all emails sent to me@domain.com with me@domain.com's PGP key, and file it into the "encrypted" IMAP folder.

But wait! This won't work yet. That is because Dovecot doesn't yet know what me@domain.com's public key is.

Go back into your server and su into the user Pigeonhole is set to run Sieve filters as. Then import the keys into the keyring:

$ gpg --recv-keys (ID of me@domain.com PGP key here)

Now you need to mark it as trusted, otherwise gpg will refuse to encrypt data with it:

$ gpg --edit-key me@domain.com
gpg> trust
  1 = I don't know or won't say
  2 = I do NOT trust
  3 = I trust marginally
  4 = I trust fully
  5 = I trust ultimately
  m = back to the main menu
Your decision? 5
Do you really want to set this key to ultimate trust? (y/N) y
gpg> save

You're done! Wait for the next incoming email, and it should be encrypted with your PGP key.

You may also want to apply this to existing emails that you've already received; well, there's a handy script for that too.

Happy encrypting!

Comments on "Encrypt specific incoming emails using Dovecot and Sieve" (38)

#1 — by Stephan Bosch

"Notice that +vnd.dovecot.filter was added to the sieve_extensions variable, not sieve_global_extensions as recommended on the extprograms plugin page. This is because we want to allow the user to customize the use of those filters."

You can avoid this using the Sieve include extension (https://tools.ietf.org/html/rfc6609). By putting the filter command into a :global included script (from which sieve_global_extensions are accessible), the user can be prevented from direct usage of vnd.dovecot.filter, but he can still control when the filter is applied by including that script where desired. Use global variables to pass any parameters needed to the included script.

#2 — by

Hi, I'm trying to implement this. I think I made all correctly but I can't understand one thing: "you should have the ability to use this in a Sieve script". Where should I paste the code of the script to be executed by devcot? Sorry I'm new to dovecot so it's probably a newby question :) Thank for you help!

#3 — by

Solved my previous problem.. thanks anyway :)

#4 — by

Hi everybody, I wanted to "force" all user's email to be encrypted (not only single users who choose to use the filter). And to do so I liked tpo use a single script (not a script for every user).

So (helped by Etienne) this is my solution (after all software has been installed). I'm using Debian: 1) In /etc/postfix/master.cf I have:

dovecot   unix  -       n       n       -       -       pipe
  flags=DROhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${user}@${nexthop} -a ${recipient}
amavis unix - - - - 2 smtp
        -o smtp_data_done_timeout=1200
        -o smtp_send_xforward_command=yes

2) In /etc/dovecot/dovecot.conf I changed the "plugin" section to:

plugin {
    sieve_plugins = sieve_extprograms
#    sieve_extensions = +vnd.dovecot.filter
    sieve_filter_bin_dir = /etc/dovecot/sieve-filters
    sieve_before = /etc/dovecot/scripts/.sieve
    sieve_global_dir =/etc/dovecot/scripts

Maybe something is not needed (but this is what I have now)

3) In /etc/dovecot/scripts/ I have a script named .sieve that contains:

require ["variables", "envelope", "fileinto", "vnd.dovecot.filter"];
if envelope :matches "to" "*" {
        set :lower "my_recipient" "${1}";
        filter "gpgit" "${my_recipient}";
        fileinto "INBOX";

I changed INBOX.encrypted to INBOX because ALL incoming messages are encripted. All users public keys are imported in the vmail user's keyring

That's all. hope could help others

#5 — by Anonymous

If gpg is installed in an unusual location such as /usr/local/bin/gpg you may have to configure the PATH when gpgit is invoked or modify the gpgit script and replace this line:

my $gpg = new Mail::GnuPG();


my $gpg = new Mail::GnuPG(gpg_path => "/usr/local/bin/gpg");

#6 — by

Dear Etienne, thank you very much for this post, I've been looking for something like this for a while.

I have a debian 7 server set up with postfix+dovecot through iRedMail, and I've followed your setup to the letter, but it still doesn't work This means the email users are virtual, and the user sieve scripts are stored in a subfolder under /var/vmail. I wonder if you have any clues as to how I can import the GPG keys for the unprivileged user actually running the Pigeonhole sieve scripts (I'm not actually sure if this is the 'dovecot', 'vmail' or 'dovenull' user), when this user doesn't have a home folder to store the gpg key info.

Is there anyway to create a log over the output of what happens with the sieve scripts when an email is received?

Any input is appreciated!

#7 — by Etienne Perot

R.T.A.: Just create a wrapper script to find out:


echo "It is $(date) and I am running as $(whoami) and my home is $HOME" > /tmp/gpgit_wrapper.log
echo "gpg says I have the following keys:" >> /tmp/gpgit_wrapper.log
gpg --list-keys >> /tmp/gpgit_wrapper.log 2>&1
echo "Now running gpgit:" >> /tmp/gpgit_wrapper.log
/path/to/real/gpgit | tee -a /tmp/gpgit_wrapper.log
echo "Exitted with return code $returnCode" >> /tmp/gpgit_wrapper.log
exit "$returnCode"

Have that be called by Dovecot instead of gpgit, then send yourself a message and check what /tmp/gpgit_wrapper.log says.

The user running the script does have to have a real home directory in order to store the GPG keys. If you don't want that, I think you can get away with just setting the GNUPGHOME environment variable and gpg will use that to store its keys instead of $HOME/.gnupg.

#8 — by

Etienne, thank you for your help! I got it working now!

My main problem was initially a typo, your script put me on the trail.

For anyone wondering how to implement this with virtual users (at least using iRedMail on Debian), the trusted keys needs to be imported to the 'vmail' user. That user doesn't have a home folder of its own, but uses the mail store path of the user receiving email as its home folder. The first email received after activating the script also causes gpg to create its configuration directory under "/var/vmail/x/x/x/x". I then imported the trusted key into the vmail user keyring by prefacing the commands listed in Etienne's guide with [sudo -s /bin/bash vmail -c' commands from guide above']. This does not work for setting the key as trusted though, so when I came to that step, I repeated the key import and set the key as trusted using a normal system user (e.g. root) and then copied the fingerprint with trust status over to the vmail user by following this guide, issuing command #3 there by prefacing with [su etc.] again (http://lists.gnupg.org/pipermail/gnupg-users/2009-November/037752.html).

Do you have any idea for a webmail solution that can decrypt the messages attached in a browser?

#9 — by Etienne Perot

R.T.A.: Not one that would be secure enough to be worth using. There are a bunch of browser extensions out there that can do PGP-in-the-browser (as opposed to PGP-in-the-webpage, which is the worst you can do if your goal is to shield yourself from the server having access to your mail). However, most of those extensions are written to work with Gmail or other popular webmail providers. I would also doubt that they isolate the decrypted payload from the DOM of the webpage itself (as doing so would probably require some tricky rendering majicks to make it look seamless), meaning that the webpage could just use some JavaScript to grab it after t's been decrypted.

tl;dr: Just use a real email client.

#10 — by

Etienne, good points indeed.

That being said, I'll just chip in that decrypting works using Horde IMP v. 5, I just verified it. That solution requires uploading the private key through Horde so that it is stored server side. One might of course argue that this defeats half the purpose of asymmetric encryption, since both keys are then stored on the server. I will use an email client personally, but if someone goes for Horde, one still needs to enter the private key passphrase to decrypt the emails, so it still adds a layer of encryption security that would have to be broken by someone getting access to the server. Just my 2 cents.

Again, thanks for the guide!

#11 — by Anonymous

This looks amazing! Exactly what i need! Unfortunately i did not find any tutorial for debian wheezy - e.g. building the extprogs was a pain and i aborted as something was still missing. Did someone manage to get this working on debian wheezy ?

#12 — by

I've managed to implement this with Ivan Spadacenta solution of applying it globally. I've stumbled upon on "little" problem. dspam isnt able to treat spam mail, because you know, they're encrypted.

We can't force the mail client (unless its a webmail) to decrypt specific messages and save on a different folder.

Any ideas?

#13 — by

@#11: Sorry, I don't use Debian. I know it's possible though because I know someone who is running such a setup. I don't know if they had to manually compile anything or not.

@#12: My solution is to have the spam filtering be itself part of the Sieve rules, and do that before encrypting the message. I have a little wrapper script at /etc/dovecot/spamassassin like so:


exec /usr/bin/vendor_perl/spamassassin -x

And then it can be used in the rules like this:

filter "spamassassin";
if header :contains "X-Spam-Flag" "YES" {
    fileinto "Spam";
# ... More rules...
filter "gpgit" "myaddress@example.com";

This way the spam doesn't get encrypted, which is good because it can then be used for training the spam filter. For false negatives (which do get encrypted), I generally move them in the spam folder anyway so that it can learn from the headers.

#14 — by


first of all, thanks for this howto. The idea is great. but:

i am getting errors to make this work on a debain wheezy.

managesieve: Fatal: Plugin 'sieve_extprograms' not found from directory /usr/lib/dovecot/modules/sieve
doveconf: Error: managesieve-login: dump-capability process returned 89
[....] Restarting IMAP/POP3 mail server: dovecotmanagesieve: Fatal: Plugin 'sieve_extprograms' not found from directory /usr/lib/dovecot/modules/sieve
doveconf: Error: managesieve-login: dump-capability process returned 89

i am allready using the unstable version of dovecot an managesieved. Does anyone have expierince to set up the plugin in wheezy.

For my observation it seems, that the error is similar to the one on archlinux: https://bugs.archlinux.org/task/35464?opened=13618&status%5B0%5D=

would be nice, if someone would help

#15 — by Etienne Perot

einsiedlerkrebs: As the error says, it can't find the sieve_extprograms plugin file. You either need to compile it yourself, or (if using Pigeonhole 0.4 or above) make sure it is compiled in the right place, using the --with-moduledir argument).

If you're installing this from a package for Pigeonhole 0.4 or above, bug your local Debian maintainer to fix it.

#16 — by

I'm very grateful for this awesome guide and the precious input and solutions by the other users. Had a bit of frustration to get it working (late night mistakes) but after re-reading carefully all the comments I got it working nicely. Unfortunately, this works only for Maildir format, I hope in the future there would be support for mbox/mdbox in Dovecot.

#17 — by

Actually nevermind my comment about the mbox/mdbox. I tested with Maildir and mdbox formats and it worked !!!!!!

#18 — by

Actually (after testing) it does work for Maildir/mbox/mdbox formats in Dovecot. It's was the encmaildir.sh that couldn't deal with mbox/mdbox. A workaround to using encmaildir.sh with mdbox for example, is to convert the latter to Maildir format (with dovecot's dsync tool), execute the script, then reconvert back to mdbox.

#19 — by John Naggets

I understand here that only the body of the mail gets encrypted. So I would be really interested to know why is not possible to also encrypt the header of the mail?

Is it a technical problem or simply too complicated?

#20 — by Etienne Perot

Yes, only the body of the mail gets encrypted. That is a consequence of using PGP for encryption; that's just how PGP email encryption works. I store email on encrypted storage (a LUKS-mounted partition or other such solution) to partially alleviate this problem. That means that emails can't get delivered after the server reboots for whatever reason, since it doesn't have the key to mount the partition again. For me it's an acceptable risk.

It wouldn't be impossible to design a system where the full email is encrypted, then decrypted with the user's login password (in fact, that's what Lavabit used to do), but I am not aware of any available open-source solution that can do this.

#21 — by

Is there also a way to use it with an S/MIME certificate instead of PGP?

#22 — by Etienne Perot

It's technically possible, but the current implementation can't.

#23 — by

I have been commenting earlier, that on my debian wheezy server the mechanism didn't work, since pidgeonhole was looking on the wrong place for ext_programms.
Yesterday with some help i luckily found out, that [i] it was a problem of debians package and [ii] the debian developer fixed it in version dovecotes 2.9.13, which is the testing branch.

So far, i am happy with that.

thank you very much for this tutorial.


#24 — by

Do you have any more specifics on what Lavabit did to secure their email? I am running a private mailserver and while the mail is stored on an encrypted filesystem, I am looking to develop a method to encrypt per-mailbox. My thought was to encrypt each piece of mail with the users public key after it passes spam filters. Then, as you seemed to suggest in your previous post, transparently decrypt it when the user IMAPs. Once the session ends, the server could reencrypt the entire mail directory.

Where I'm a bit unsure is in details. A private key would have to be stored on the server. This key would have to be different from the users primary PGP key. It would obviously be protected by a passphrase. What do you think of the idea of generating a new PGP keypair when a user changes his password? That way, the password is only passed from the user, through the SSL/TLS channel to dovecot which then passes the password to the encryption engine which passes it to the key. Is there a better method of securely translating a changeable password to a private PGP passphrase?

Another option might be to use a client certificate based system, but I'm a bit foggy on that idea.

Obviously, this isn't a perfect system because your private key is on a server. In addition, anyone with access to your IMAP account via a stolen phone or laptop could decrypt your mail. However, it does prevent the system administrator from giving up your email. Also it would give the account owner to remotely delete the key and any copies he has and make the entire mailbox useless bits.

I'm talking this idea over with a few people but since you've implemented a similar system yourself, I'm very curious to get some feedback on the architecture. I'm a competent enough programmer and have confidence it could be done. What I don't have confidence in is the crypto theory. I could very well be overlooking something quite obvious to a professional security person.

In any case, thanks for this writeup.

#25 — by

I got it working by updating dovecot to see

plugin { sieve_plugins = sieve_extprograms } without error

add-apt-repository ppa:malte.swart/dovecot-2.2 aptitude update aptitude upgrade

a PHP script can use PHP functions for S/MIME, but I would rather use GPG for full control of keys.

#26 — by

My user scripts include this

require ["include","variables"];
# rule:[gpg encypt incoming]
if header :contains "to" "michael"
    include :global "gpg";

or this

require ["include","variables"];
# rule:[smime encypt incoming]
if header :contains "to" "michael"
    include :global "smime";

My added Dovecot plugin settings

plugin {
        sieve_plugins = sieve_extprograms
        sieve_global_extensions = +vnd.dovecot.filter
        sieve_filter_bin_dir = /etc/dovecot/sieve-filters
# Directory for :global include scripts (not to be confused with sieve_global_path).
  # If unset, the include fails.
        sieve_global_dir = /etc/dovecot/sieve

my sieve_global_dir = /etc/dovecot/sieve .sieve scripts smime.sieve

require ["variables", "vnd.dovecot.filter"];
# rule:[noti3]
if header :matches "Delivered-To" "*"
        filter "smimeit" "${1}";


require ["variables", "vnd.dovecot.filter"];
# rule:[noti3]
if header :matches "Delivered-To" "*"
        filter "gpgit" "${1}";

This is how I got a user uploadable script that works for roundcube's managesieve plugin for user edits.

#27 — by

I installed everythink. A little bit different because I am Using Ubuntu 14.04 with Plesk 12.

I did not compile the extprograms because they should be part of dovecote and pigeonhole in ubuntu 14.04 as far as I know. I found /usr/lib/dovecot/modules/sieve/lib90_sieve_extprograms_plugin.so.

I was facing the sam error like Post #14. I solved it by creating an symlink from ln -s /usr/lib/dovecot/modules /usr/lib/dovecot/modules/sieve.

Then I could restart dovecot without any error. I put the gpgit script directly at /etc/dovecot/sieve-filters/. The PGP-pub-key is imported and hast ultimately (5) trust. The Filter is running without errors and moving the Mails to INBOX.encrypted but the mails are not becomming encrypted.

Part from Maillog: May 7 13:37:46 kunden dovecot: service=lda, user=info@email-pub-key-exists.com, ip=[]. sieve: msgid=: stored mail into mailbox 'INBOX.encrypted'

Do you have any ideas? Maybe it is because all files are owned by root? But then there shouldb be a permision error in the log.

Here are some links to ohter tutorials (maybe helpful for someone): http://www.debian.pl/entries/348-Automatyczne-szyfrowanie-przychodz%C3%84%E2%80%A6cych-emaili-przy-u%C3%85%C2%BCyciu-Dovecot-oraz-Sievehttps://blog.imirhil.fr/chiffrez-vos-courriels-entrants-gpgit.html

#28 — by

Hi Etienne, is this tutorial still up to date? I can't seem to find dovecot-config. there's nothing like that in /usr/lib/dovecot :(

#29 — by

I am Using Ubuntu 14.04 LTS as an Email server updating dovecot with

add-apt-repository ppa:malte.swart/dovecot-2.2
aptitude update
aptitude upgrade

was all I needed

all my custom dovecot config settings are in /etc/dovecot/conf.d/99-mail-stack-delivery.conf

#30 — by

Hi Carl,

From the Dovecot wiki

Basic Configuration

This page tells you the basics that you'll need to get a working Dovecot installation.

Find Dovecot configuration file location using:

doveconf -n | head -n1

Your configuration file doesn't exist if you installed Dovecot from sources. The config directory should contain a README file pointing to an example configuration, which you can use as your basic configuration. For example:

cp -r /usr/share/doc/dovecot/example-config/* /etc/dovecot/

The default configuration starts from dovecot.conf, which contains an !include conf.d/*.conf statement to read the rest of the configuration. This split of configuration files isn't a requirement to use, and it doesn't really matter which .conf file you add any particular setting, just as long as it isn't overridden in another file. You can verify with doveconf -n that everything looks as you intended.

#31 — by

To comment on the previous suggestions to encrypt the whole email (including the headers), one possibility would be to use EncFS which is powerful nifty tool that can encrypt selected folders or even the whole user homedir. The way it works, the encrypted folder could be either automatically mounted (decrypted) upon login, or manually, using the user's set password. The process could be coupled with some shell scripting or php. So for example when the user logs into his account via roundcube, the server would mount/decrypt his folder so dovecot/postfix could access the emails, and as soon as user logs out, the folder is dismounted and encrypted again. I guess the same process could be applied for POP3/IMAP access. Something to think about. And what's really nice about EncFS, it can be configured to even disallow root any access to potentially decrypt the folder :)

#32 — by

What would actually need to be done, if I would like to automatically encrypt also all unencrypted outgoing emails? I would like to avoid a cronjob searching through IMAP folders but instead immediately encrypt while writing to the 'sent' folder.

Thanks a lot for this very useful discussion of automatic maildir encryption.

#33 — by

Hi Etienne I'm using gpgit with dovecot as described here. Currently I'm having problems with incoming mails that are signed with PGP/MIME Such mails are encrypted using gpgit. But when I try to open such a mail in my thunderbird+enigmail it ends up in a loop. If the signature is performed with inline pgp then it's no problem to open it in TB after server side encryption done by gpgit

#34 — by

Hi Etienne,

Are you still using this system (or one like it)? What problems have you met? How have other (not your users) email correspondents found it?

Clearly, it's intrinsically flawed - not least because it relies on PGP/GPG, which doesn't encrypt RFC*822 headers. But I think it's got to be better than nothing. The default at the moment is that your postmaster can read any of your mail that wasn't deliberately GPG-encrypted by the sender.

#35 — by

Hi Denton,

I am still using this system, although this post is a bit outdated on the way to set it up (mostly around the dovecot/sieve setup part).

It is indeed intrinsically flawed in the ways you describe. However, the "postmaster being able to read your email" part isn't much of a concern to me or to most readers of this post, since they presumably already are postmaster on the machine they would be setting this up on.

#36 — by Anonymous

TREES and scrambler are plugin for Dovecot to encrypt mailboxes. It still makes sense to encrypt the block device with LUKS but the plugins encrypt the emails with individual keys for each user. Lavabit also released its software magma under a Free Software license. I would still recommend OpenPGP because the decryption is performed on the computer of the recipient and not the server. The only advantage of software like TREES, scrambler and magma is that they are transparent. You can also argue that they make attacks for difficult.

#37 — by

Thanks for the great software. I was able to install that mail encryption on an OpenBSD-Server. Now I would like to encrypt the Sent-Folders, too. Unfortunately the encmaildir.sh script does not work because of OpenBSD's "find" implementation not using regular expressions. Is there I way to use just Postfix or Dovecot to encrypt the Sent folder or is it the only or better way to reshape the encmaildir.sh for use on OpenBSD?

#38 — by

Lukas: The "sent" folder does not pass through Sieve filters, so there is no way to have Dovecot automatically encrypt it, or at least not using Sieve filtering.

I think the best way to get this working would be to make encmaildir.sh work with OpenBSD's find implementation, and then set it up to run every once in a while via cron.

Replying to: Encrypt specific incoming emails using Dovecot and Sieve