Search code examples
pythonemailencryptiongnupg

How to send gpg encrypted email (with attachment) using python


I have a script that I want to email newly generated files in a given folder. I have been able to generate and send emails (without encryption) by using smtplib, email and uu. I also managed to successfully send a gpg encrypted without attachments.

Sending a gpg encrypted email with attachment has been a challenge however.

I used the python-gnupg library to create a cipher text for the file and thought I could just email that as the body of the email. This is along the lines of what I tried.

from email.mime.text import MIMEText
import gnupg
gpg = gnupg.GPG(gnupghome=GPG_HOME_HOME)
with open(FILE_PATH, "rb") as f:
    cipher_text = str(gpg.encrypt(FILE_PATH.read(), RECIPIENT_EMAIL)

msg = MIMEText(cipher_text, "plain")
msg["Subject"] = "***TEST***"
msg["From"] = EMAIL_SENDER
msg["To"] = EMAIL_RECIPIENT
msg_text = msg.as_string()

I also tried to adjust the example at https://docs.python.org/release/3.5.3/library/email-examples.html to my own needs but I have not been successful with that either.

My gpg is set up correctly and I'm able to send/receive gpg encrypted emails just fine with thunderbird/enigmail.

Could someone please tell me how to send a gpg encrypted email with attachment? I believe this requires some low-level manipulation of the email structure but I'm not too familiar with that.

Thanks,


Solution

  • This is what I managed to come up with to get a working gnupg encrypted email with attachment. I used an email sent from thunderbird using enigmail as a template.

    from email.mime.base import MIMEBase
    from email.message import Message
    import base64
    import mimetypes
    import os
    
    import gnupg # python-gnupg
    
    def get_email_string(email_address_recipient, file_path_attachment, email_message=""):
        def get_base64_file(file_path):
            with open(file_path, "rb") as f:
                b_str = base64.b64encode(f.read())
            return b_str
    
        def get_mimetype(file_path):
            return mimetypes.guess_type(file_path)[0]
    
        def get_file_name(file_path):
            return os.path.basename(file_path)
    
        def get_gpg_cipher_text(string, recipient_email_address):
            gpg = gnupg.GPG(gnupghome=DIR_GNUPG)
            encrypted_str = str(gpg.encrypt(string, recipient_email_address))
            return encrypted_str
    
    
        msg = Message()
        msg.add_header(_name="Content-Type", _value="multipart/mixed", protected_headers="v1")
        msg["From"] = EMAIL_FROM
        msg["To"] = email_address_recipient
    
        msg_text = Message()
        msg_text.add_header(_name="Content-Type", _value="multipart/mixed")
        msg_text.add_header(_name="Content-Language", _value="en-US")
    
        msg_body = Message()
        msg_body.add_header(_name="Content-Type", _value="text/plain", charset="utf-8")
        msg_body.add_header(_name="Content-Transfer-Encoding", _value="quoted-printable")
        msg_body.set_payload(email_message + 2*"\n")
    
        msg_attachment = Message()
        msg_attachment.add_header(_name="Content-Type", _value=get_mimetype(file_path_attachment), name=get_file_name(file_path_attachment))
        msg_attachment.add_header(_name="Content-Transfer-Encoding", _value="base64")
        msg_attachment.add_header(_name="Content-Disposition", _value="attachment", filename=get_file_name(file_path_attachment))
        msg_attachment.set_payload(get_base64_file(file_path_attachment))
    
        msg_text.attach(msg_body)
        msg_text.attach(msg_attachment)
        msg.attach(msg_text)
    
    
        pgp_msg = MIMEBase(_maintype="multipart", _subtype="encrypted", protocol="application/pgp-encrypted")
        pgp_msg["From"] = EMAIL_FROM
        pgp_msg["To"] = email_address_recipient
    
        pgp_msg_part1 = Message()
        pgp_msg_part1.add_header(_name="Content-Type", _value="application/pgp-encrypted")
        pgp_msg_part1.add_header(_name="Content-Description", _value="PGP/MIME version identification")
        pgp_msg_part1.set_payload("Version: 1" + "\n")
    
        pgp_msg_part2 = Message()
        pgp_msg_part2.add_header(_name="Content-Type", _value="application/octet-stream", name="encrypted.asc")
        pgp_msg_part2.add_header(_name="Content-Description", _value="OpenPGP encrypted message")
        pgp_msg_part2.add_header(_name="Content-Disposition", _value="inline", filename="encrypted.asc")
        pgp_msg_part2.set_payload(get_gpg_cipher_text(msg.as_string(), email_address_recipient))
    
        pgp_msg.attach(pgp_msg_part1)
        pgp_msg.attach(pgp_msg_part2)
    
        return pgp_msg.as_string()