Sending secure certified (S/MIME) emails via php using phpmailer

there is very little on the internet about this topic it seems.  the following has been tested on a modern ubuntu 10 server running plesk 10.

update january 2013 : note: if you are using phpmailer greater than v5.2.2 or are accessing the phpmailer code from github then please read my notes at the bottom of the document before continuing!

i must thank Geoff Millikan-2 and his post here for a lot of the info on how to do this.  I am to a large degree replicating his experience, just updating it a little for 2012.

the following steps assume you have a unix/linux web server and that you have openssl installed on it. they also assume your using php 5 and a recent copy of phpmailer for the integration.  testing was done with the opensource free version of phpmailer,  v5.2.1.  note; i had prior to using this version also tried with 5.1 and the procedure didn’t work, so be warned!  go and get the latest copy of phpmailer here.

  1. get a personal email certificate.  thawte used to do them but discontinued a while ago. comodo still do them as of writing this and you can sign up here.   note; do this step in a web browser you know you can easily export certificates from. im going to assume your using internet explorer (sorry!) which exports in the pfx format.  the massive majority of what follows is still valid though if you aren’t using IE.
  2. once you have the email certificate installed in your browser you need to export it.  to do this from IE follow these steps here.
  3. now upload this pfx file to your web server.  you should upload it somewhere outside of the public web space.  note; updates to your open_basedir php setting may be requrired to allow php access to this folder.
  4. assuming you have a pfx (pkcs12) encrypted file produced by the steps above read this step in full.  if you already have a pem format certificate then upload this to your webserver and skip the rest of this step.

    on your server access the shell and in the secured folder where the uploaded pfx file sits we will now use a handy utility that comes as part of openssl.  the command allows us to convert our pfx file into a format that we can use with the phpmailer software.  

    using the below examples as references be sure to update xxx.pfx to the name of your uploaded certificate and yyy.pem to the name of the destination certificate file that will be referenced by the php code:

    openssl pkcs12 -in xxx.pfx -out yyy.pem

    you will be asked for passwords by openssl during this procedure that you had entered during the certificate export process from the browser explained in step 2.  enter these and you should now find you have the .pem file on your server!

    2020 Update: if you have a .p12 certificate you likely need to generate a certfile, keyfile etc in which case use these the commands below.  A big thanks to Gunnar Gläser for sharing!

    for the certfile:
    openssl pkcs12 -in certificates.p12 -clcerts -nokeys -out cert.crt -passin pass:

    for the keyfile:   
    openssl pkcs12 -in certificates.p12 -nocerts -out cert.key -passin pass: -passout pass:YourPassword

    for the extrafile:
    openssl pkcs12 -in certificates.p12 -cacerts -nokeys -out ca-root.crt -passin pass: -passout pass:YourPassword  

    To sign within PHPMailer
    $mail->sign(’/pathto/cert.crt’, ’/pathto/cert.key’, ’YourPassword’, ’/pathto/ca-root.crt’);

  5. now you need just modify a few lines in the main class.phpmailer.php file. firstly find the three lines near the top of the file that declaire $sign_cert_file, $sign_key_file and $sign_key_pass. change each line from protected to public.  you should end up with...

    public   $sign_cert_file = ’’; 
    public   $sign_key_file  = ’’; 
    public   $sign_key_pass  = ’’; 
  6. now locate the line that should appear as: 

    if (@openssl_pkcs7_sign($file, $signed, "file://".$this->sign_cert_file, array("file://".$this->sign_key_file, $this->sign_key_pass), null)) {

    and change it to

    if (@openssl_pkcs7_sign($file, $signed, file_get_contents($this->sign_cert_file), array(file_get_contents($this->sign_key_file), $this->sign_key_pass), null)){
  7. a final optional change to the file is to control where temporary files are created during the generation of the encrypted email body by the phpmailer class.  as default the script attempts to write to the folder where the actual phpmailer class file resides.  i found this was far from ideal as I didnt want to give write permissions to that particular folder.    

    so if you want to write temporary files else where locate the two lines that include the command tempnam and change as required.  Here you can see I have set them to write to a sub directory called tmp.   So the two lines were....

    $file = tempnam(’’, ’mail’);
    $signed = tempnam("", "signed");

    and they became...

    $file = tempnam(’./tmp/’, ’mail’);
    $signed = tempnam("./tmp/", "signed");
  8. now you just need to add a few extra commands to the php mailer class when you actually send a mail. below is the code of a very simple email test and the three lines I have added to use the certificate file are marked by comments and appear just before the actual sending:

    $mail = new PHPMailer();

    $mail->Subject = "Test 1";
    $mail->Body = "Test 1 of PHPMailer.";


    $mail->Send(); // Send encrypted email!

    the xxx in the example above should be replaced by the full server path to the folder containing your certificate file.  

    the yyy in the example above needs replacing with the private key password that was defined during the creation of your certificate.

If you need any help sending certificate encrypted emails from php (with or without phpmailer) get in touch!

january 2013 update:

Marcus Bointon the maintainer of the github version of the phpmailer source code emailed me to tell me that the source had been updated by him to ease the process of sending secure mails somewhat meaning that modifications to the phpmailer codebase shouldn’t be needed at all.  in his words:

As far as your article goes, you can do what you say without having to alter the PHPMailer code at all. I think you were using an old version when you wrote step 5 as that changed a year or so ago, but it should be done using the Sign() method anyway, which accesses the properties for you, so it didn’t matter that they were protected before. 

I don’t know why you needed to do step 6 - stream wrappers work fine for me.

Step 7 is fixed by my recent changes, which use the system temp directory.

created: 18/4/12
last updated: 10/2/13