I've been trying to concoct a PHP script to take a sensitive message, encrypt it using someone's public key and email it. I've looked at three different approaches I've found through google search and none seems to work

Approach 1
The first uses [man]openssl_pkcs7_encrypt[/man] and I followed the sample script here. Two problems. First, the encryption is not very strong:

openssl_pkcs7_encrypt() takes the contents of the file named infile and encrypts them using an RC2 40-bit cipher so that they can only be read by the intended recipients specified by recipcerts.

40-bit cipher doesn't sound particularly strong.
Secondly, the script doesn't work:

$headers = array(
	"From" => "sender@example.com",
	"To" => "recipient@example.com",
	"Subject" => "Encrypted mail test",
	"X-Mailer" => "PHP/".phpversion()
);

define("PUBLIC_KEY_PATH", "/path/to/public_key.pub");

// Get the public key certificate.
$pubkey = file_get_contents(PUBLIC_KEY_PATH);

// Remove some double headers for mail()
$headers_msg = $headers;
unset($headers_msg['To'], $headers_msg['Subject']);

$data = "This email is Encrypted!\nYou must have my certificate to view this email!\nMe\n";

//write msg to disk
$msgfile = "/path/to/msg.txt";
file_put_contents($msgfile, $data);

// Encrypt message
$encrypted_msgfile = "/path/to/encrypted_msg.txt";
$crypt_result = openssl_pkcs7_encrypt($msgfile, $encrypted_msgfile, $pubkey, $headers_msg, PKCS7_TEXT, 1);
if ($crypt_result === FALSE) {
	die ("encryption failed\n");
}

// Separate headers and body for mail()
$data = file_get_contents($encrypted_msgfile);

$parts = explode("\n\n", $data, 2);

// Send mail
mail($headers['To'], $headers['Subject'], $parts[1], $parts[0]);

It dies with this error:

error:0906D066:PEM routines:PEM_read_bio:bad end line
encryption failed

I'm not certain, The problem might be with the $pubkey param I supply to openssl_pkcs7_encrypt -- this file is apparently not x.509. The key file looks something like this:

-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2.0.17 (MingW32)

mQINBFE2oIsBEACo1d7vsGdmWJqUHSHsDcOH8ZL+YJbNrghnvRe2V1QNF83JsF+C
LHfL3piH4MZ1FEQsAyUfqn7fqagIU0KrrsT4MeCR8msYAE64cTkwkT4J4Y4wEybj

...etc..

Pqvo9g9u2Vswm11CHZs9QsXm/9+5qf9Ww3ycZfmAOM8jkrtpO9gdpDF9zfmCQiVc
HX3gATz3HZoheHhOIA==
=Xnkl
-----END PGP PUBLIC KEY BLOCK-----

Approach 2
Although it doesn't mention email, I tried using the mcrypt sample code given in the manual for [man]mcrypt_module_open[/man]. That script does encrypt and decrypt some data but it does so by generating its own key for some reason:

/* Create key */
$key = substr(md5('very secret key'), 0, $ks);

And the key is quite short (32 chars in length). It's not clear to me if I would be allowed to use a public key belonging to the recipient. Additionally, the encrypted message appears to be binary instead of ASCII armored of base 64 encoded and it also lacks any sort of emailable headers to describe the encryption technique or whatever that would lend any clues to a remote email client. I tend to doubt a mail client would be able to make sense of the cipher. The documentation on mcrypt really seems to assume you know exactly what it is for.

Approach 3
The last approach is from an IBM article and relies on command line use of gpg to create the encrypted message. While I believe this approach would generate nice useful informative header information about the encrypted content, the script doesn't seem to allow me to specify a public key file directly and instead relies on me to manipulate the appropriate keyrings to manage keys for both the sender and the recipient. This script fails:

$from = "sneakyimp"; // local username on my ubuntu box
$to = "recipient@example.com"; // some email address

//cut the message down to size, remove HTML tags
$messagebody = "Here is my encrypted message.\nI have defined it in PHP and encrypted it using the example at http://www.ibm.com/developerworks/library/os-php-encrypt/index.html\n\nHope this works!";
$escaped_body = escapeshellarg($messagebody);

$gpg_path = '/usr/bin/gpg'; // this may vary from distro to distro
$home_dir = '/path/to/test/dir';
$user_env = 'sneakyimp';

$cmd = "echo $escaped_body | HOME=$home_dir USER=$user_env $gpg_path" .
	"--quiet --no-secmem-warning --encrypt --sign --armor " .
	"--recipient $to --local-user $from";

echo "cmd:\n";
echo $cmd . "\n\n";

$message_body = `$cmd`;

mail($to,'Encrypted Message', $message_body,"From:$from\r\n");

The approach obviously doesn't let me manually specify the location of the public key file for recipient@example.com This script produces an error:

cmd:
echo 'Here is my encrypted message.
I have defined it in PHP and encrypted it using the example at http://www.ibm.com/developerworks/library/os-php-encrypt/index.html

Hope this works!' | HOME=/path/to/test/dir USER=sneakyimp /usr/bin/gpg --quiet --no-secmem-warning --encrypt --sign --armor --recipient recipient@example.com --local-user sneakyimp

gpg: skipped "sneakyimp": secret key not available
gpg: [stdin]: sign+encrypt failed: secret key not available

Can anyone refer me to a working PHP script that accomplishes this? Or perhaps help me sort the issue? It seems to me it would be very useful to have a script that can send an encrypted message. I'm very much interested in portability and would like to avoid any steps where one must manipulate the keyring belonging to a user and one could instead specify a specific key file, etc. Any advice would be much appreciated.

    OK so I pulled out my copy of Bruce Schneier's Applied Cryptography (an really good book) and has a very helpful section on Privacy-Enhanced Mail(PEM) which I've been looking at. Turns out PEM is not just way to encrypt things but it describes a variety of privacy-related protocols. To summarize Schneier:

    PEM is the Internet Privacy-Enhanced Mail standard adopted by the Internet Architecture Board (IA😎 to provide secure electronic mail over the Internet...The PEM protocols provide for encryption, authentication, and key management.

    The basic idea is that it describes protocols for sending encrypted and/or signed messages while also describing what has gone into encrypting and/or signing them. It supports both symmetric and public key encryption. The Wikipedia link above says that it was not widely adopted due to its reliance on a centralized root CA.

    I think this cocktail of encryption-plus-protocol is what I'm after. Schneier also has a section on PGP (which uses web-of-trust rather than centralized CA) but details about the protocol look pretty light.

    Seems to me that if I want to have PHP sending these messages, I will probably have two choices:
    1) concoct awkward CLI stuff in PHP and use exec to manipulate key rings and such to get gpg to encrypt my messages (and then take care to shred and/or cleanup the file system to make sure my sensitive messages don't hang around
    2) use mcrypt or some other PHP extension to encrypt my messages and then write PHP code to build out the appropriate email-friendly protocol aspects described by PEM and/or PGP such that the message, when it arrives at somebody's mail client, will be decipherable by something like Enigmail or some other secure mail client/plugin.

    Surely someone has done this in PHP before?

      sneakyimp wrote:

      It's one of those modules with really spare documentation -- not much detail. And not many examples.

      You might have to go to the GPGME manual and try to map what's there onto the PECL extension's interface. The tedious part of course is that the GPGME manual is written for the benefit of the PECL extension's writer, rather than the extension's user.

        Thanks, weedpacket, for the info.

        For anyone who is interested, I was able to install gnupg, a PECL extension, by doing the following on my Ubuntu 11.04 machine:

        # required by pecl to install gnupg:
        sudo apt-get install libgpgme11-dev
        # luckily, pecl command is available on my workstation so I don't have to compile on my own
        sudo pecl install gnupg
        

        Then, create a new ini file, /etc/php5/conf.d/gnupg.ini containing this one line:

        extension=gnupg.so

        Restart apache

        sudo /etc/init.d/apache2 restart

        Check phpinfo() output for gnupg support.

          6 days later

          OK so I've been reading that link Weedpacket sent along and see that it has key management functions but I don't see all of those implemented in the PHP extension -- e.g., I can't see any way to list one's keys.

          I also get the impression that apache will need its own keyring. Interestingly, assuming the role of www-data on my ubuntu workstation didn't exactly have the desired effect:

          sneakyimp@ubuntu-64:/var/www/erep_v2$ sudo su www-data
          [sudo] password for sneakyimp: 
          $ whoami
          www-data
          $ gpg --list-keys
          gpg: fatal: can't create directory `/var/www/.gnupg': Permission denied
          secmem usage: 0/0 bytes in 0/0 blocks of pool 0/32768
          

          I wonder how secure it is to keep keys in apache's keyring and for apache's keyring to be in /var/www? I expect I'll just be importing public keys from elsewhere so it sounds reasonably safe to me. I suppose I would feel a bit safer if it were not in /var/www but I don't know of any PHP.ini settings or apache settings that might let me put it somewhere else.

            What is www-data's home directory? IIRC, that's the first (only?) default location it read/writes the '.gnupg' directory (which seems to be a common paradigm for many other *nix apps).

            EDIT: Try exporting the env variable 'GNUPGHOME' and pointing it somewhere other than your 'www' directory.

              bradgrafelman;11025075 wrote:

              What is www-data's home directory? IIRC, that's the first (only?) default location it read/writes the '.gnupg' directory (which seems to be a common paradigm for many other *nix apps).

              sneakyimp@ubuntu-64:/var/www/erep_v2$ sudo su www-data
              [sudo] password for sneakyimp: 
              $ ls -dl ~
              drwxr-xr-x 18 root root 4096 2013-01-18 13:54 /var/www

              It would appear that the home dir for www-data is /var/www (which on my machine is owned by root:root). Seems like it would be quite easy for the .gnupg directory to find itself in the web root of some domain in this location and that doesn't sound particularly secure -- if www-data has any private keys. On the other hand, I just looked at the contents of the files in my ~/.gnupg directory and it looks at least to be binary, possibly encrypted.

                woot! I have run this script from the command line on my workstation and it results in an encrypted message being sent using my gmail account. If I use Thunderbird (with Enigmail installed) to check my gmail account, I receive the message and decrypt it via the available OpenPGP functionality. I do get a notice/warning/error in Thunderbird that the signature cannot be verified but this is because I did not sign the encrypted message before sending it. It occurs to me that I don't know whether to use a public key or private key for signing messages.

                Anyway, this script imports a public key into somebody's keyring:

                <?php
                
                // to send encrypted mail to someone, you must first add their public key to your keyring
                // it will end up in the keyring of the user who runs this script (e.g., www-data or sneakyimp
                // in my case, depending on whether I run it from CLI or via Apache)
                
                
                // path to a file containing a recently created public key.
                // NOTE that I created this key on my windows machine
                // using Enigmail/OpenPGP functions and saved it in a file
                // on my Ubuntu workstation using SSH
                define("PUBLIC_KEY_PATH", "/home/sneakyimp/cryptmail/recipient_public_key.pub");
                
                // import the key into your keyring (uses OOP style)
                // importing a key twice will not cause an error and will again
                // return an array.
                $gpg = new gnupg();
                $public_data = file_get_contents(PUBLIC_KEY_PATH);
                $info = $gpg->import($public_data);
                var_dump($info);
                

                Once you've done that, you should be able to see the imported key in your keyring. This command is helpful:

                gpg --list-keys --fingerprint

                To get information on the key using PHP, you can refer to it using a variety of different identifiers.

                // get key info (uses procedural style)
                $gpg = gnupg_init();
                // use the 32-byte full fingerprint, or the 8-byte short Key ID,
                // even partial owner names will work if the key is one suitable for encryption
                // obviously, the more specific you are, the less like you are to get something wonky
                $info = gnupg_keyinfo($gpg, '23A423BE');
                print_r($info);
                

                Once the public key is in your keyring, you can use it to encrypt a message and send it via mail. This script requires PEAR::Mail to be installed but worked like a charm for me and sent the email message via a gmail account:

                try {
                	$gpg = new gnupg();
                	$gpg -> seterrormode(gnupg::ERROR_EXCEPTION); // throw an exception in case of an error
                	$gpg -> setarmor(1); // enable armored output -- results in ASCII text rather than binary data;
                	$gpg->addencryptkey("23A423BE"); // this is my public Key Id.
                //	$gpg->addencryptkey("recipient@example.com"); // this type of reference can also work YMMV
                
                // this is encrypted, but not signed.  you also need a private key to sign.	
                $encrypted = $gpg->encrypt("Jaith, this is that secret message I was a-talking about");
                
                
                // code that follows requires you to have PEAR mail installed	
                require_once "Mail.php";
                
                $params = array(
                	"host" => "smtp.gmail.com",
                	"port" => 587,
                	"auth" => TRUE,
                	"username" => "sender@gmail.com",
                	"password" => "someP4ssW0rd",
                	"debug" => TRUE
                );
                $smtp = Mail::factory('smtp', $params);
                if (PEAR::isError($smtp)){
                	throw new Exception("Unable to create pear MAIL object");
                }
                $headers = array(
                	"From" => "sender@gmail.com",
                	"Sender" => "sender@gmail.com",
                	"Reply-to" => "sender@gmail.com"
                );
                $send_result = $smtp->send("recipient@example.com.com", $headers, $encrypted);
                if ($send_result !== TRUE){
                	echo "send failure:\n";
                	var_dump($send_result);
                	echo "\n\n";
                }
                
                echo "\n\nTHAT IS ALL\n\n";
                } catch (Exception $e) {
                	echo "error:" . $gpg->geterror();
                	die("Exception:" . $e->getMessage());
                }
                
                
                  Write a Reply...