GnuPG Encryption with PHP

I found PHP’s documentation on the GnuPG functions to be pretty sparse, so thought I would share some specific steps that I went though in order to get everything working.

Prerequisites

First off, you have to install the GnuPG PHP libraries through pecl. It requires the GnuPG Made Easy (gpgme) packages to get working. The following shell commands will install the OS packages, install the GnuPG PHP libraries, then enable the PHP extension and restart Apache:

# apt-get install gnupg gpgme gpgme-devel

# pecl install gnupg

# echo extension=gnupg.so > /etc/php.d/gnupg.ini

# apachectl restart

Creating GnuPG Keys

Next, you need to create a set of keys to encrypt and decrypt your data. You’ll need to put the keys somewhere where the webserver can read and write to a directory. I’ll use /var/www/.gnupg since that is the default home directory for many Apache installations. After running the gpg command, answer the questions as prompted. User input is red in the output shown below.

# mkdir -p /var/www/.gnupg

# gpg --homedir /var/www/.gnupg --gen-keygpg
WARNING: unsafe permissions on homedir `/tmp/keys'

gpg (GnuPG) 1.4.5; Copyright (C) 2006 Free Software Foundation, Inc.
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions. See the file COPYING for details.
gpg: keyring `/tmp/keys/secring.gpg' created
gpg: keyring `/tmp/keys/pubring.gpg' created
Please select what kind of key you want:
   (1) DSA and Elgamal (default)
   (2) DSA (sign only)
   (5) RSA (sign only)
Your selection? 1
DSA keypair will have 1024 bits.
ELG-E keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 2048
Requested keysize is 2048 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 10y
Key expires at Fri Feb 23 16:35:14 2018 PST
Is this correct? (y/N) y
You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
    "Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"
Real name: Some User
Email address: some@user.com
Comment: This is a key for Some User
You selected this USER-ID:
    "Some User (This is a key for Some User) <some@user.com>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
You need a Passphrase to protect your secret key. Enter your passphrase here
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: /tmp/keys/trustdb.gpg: trustdb created
gpg: key 21CCC3D6 marked as ultimately trusted
public and secret key created and signed.
.... a bunch of random characters here....
gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: next trustdb check due at 2018-02-24
pub   1024D/21CCC3D6 2008-02-27 [expires: 2018-02-24]
      Key fingerprint = FA45 1EE9 8772 70EF 1CFA  99CE 048A 6139 21CC C3D6
uid                  Some User (This is a key for Some User) <some@user.com>
sub   2048g/A83E754B 2008-02-27 [expires: 2018-02-24]
#chown -R apache:apache /var/www/.gnupg

Make note of the key fingerprint in the 4th from the bottom line. You’ll need this in your PHP code when referencing the key. Also, make sure that you write down your pass phrase somewhere. Your encrypted data will be useless if you don’t have the pass phrase.

Your Application

Now you can write your PHP code that will do the encryption. Here is a sample that encrypts, then decrypts something:

<?php
$CONFIG['gnupg_home'] = '/var/www/.gnupg';
$CONFIG['gnupg_fingerprint'] = 'FA451EE9877270EF1CFA99CE048A613921CCC3D6';

$data = 'this is some confidential information';

$gpg = new gnupg();
putenv("GNUPGHOME={$CONFIG['gnupg_home']}");
$gpg->seterrormode(GNUPG_ERROR_SILENT);
$gpg->addencryptkey($CONFIG['gnupg_fingerprint']);
$encrypted =  $this->gpg->encrypt($data);
echo "Encrypted text: \n<pre>$encrypted</pre>\n";

// Now you can store $encrypted somewhere.. perhaps in a MySQL text or blob field.

// Then use something like this to decrypt the data.
$passphrase = 'Your_secret_passphrase';
$gpg->adddecryptkey($CONFIG['gnugp_fingerprint'], $passphrase);
$decrypted = $gpg->decrypt($encrypted);

echo "Decrypted text: $decrypted";
?>

It would be best to store $passphrase somewhere completely separate from your application configuration. Perhaps an admin user would be required to enter the passphrase when looking up this information. That way your passphrase is not stored in your config file or anywhere that an attacker could potentially gain access to it.

Troubleshooting

Make sure that the web server can write to the GnuPG Home directory. This obviously is not ideal, but seems to be required in the testing that I have done. I’ve been able to set ‘secring.gpg’ to be owned by root, but that does little good since the directory it is in has to be writable.

You can raise the error mode to GNUPG_ERROR_WARNING to generate PHP warnings on GnuPG errors. That might help to track down where errors are occurring

17 thoughts on “GnuPG Encryption with PHP”

  1. Brandon, thanks for writing a great tutorial! Unfortunately for me I came across it after setting up gnu/php on my server… This would have saved me countless hours of guess-work.

  2. Great help instructions but left out critical piece of info.

    It didn’t work until I discovered the /home/user/.gnupg directory needs to be world writable.

  3. I have a step in there that chown’s the directory to be owned by Apache (assuming your web server runs as the user ‘apache’).

    #chown -R apache:apache /var/www/.gnupg

    It shouldn’t have to be world writable, just writable by the user that your web server is running as.

  4. Those who can encrypt and decrypt via commandline but can’t decrypt via php called from a webbrowser want to try out the –no-tty parameter for gpg:
    php-script to decrypt encrypted.gpg to decrypted.txt

  5. I’m having a problem getting keys as well:

    [root@mirage enrollment]# php encrypt.php
    PHP Warning: gnupg_addencryptkey(): get_key failed in /var/www/vhosts/madeupdomain.com/httpdocs/enrollment/encrypt.php on line 10
    PHP Warning: gnupg_encrypt(): no key for encryption set in /var/www/vhosts/madeupdomain.com/httpdocs/enrollment/encrypt.php on line 13
    GNUPGHOME set to: /var/www/.gnupg

    [root@mirage enrollment]# gpg –homedir /var/www/.gnupg –fingerprint
    gpg: WARNING: unsafe ownership on homedir `/var/www/.gnupg’
    /var/www/.gnupg/pubring.gpg
    —————————
    pub 1024D/24F0C25E 2009-10-29
    Key fingerprint = 61EC B598 C01B 3DD4 2543 FB1A 1A16 8972 24F0 C25E
    uid dummy
    sub 2048g/3F9A5055 2009-10-29

    [root@mirage enrollment]# cat encrypt.php
    seterrormode(gnupg::ERROR_EXCEPTION); // throw an exception in case of an error

    // set the environment so gnupg can find the keyring
    putenv(“GNUPGHOME=/var/www/.gnupg”);

    $res = gnupg_init();
    gnupg_seterrormode($res,GNUPG_ERROR_WARNING); // raise a PHP-Warning in case of an error
    gnupg_addencryptkey($res,”61ECB598C01B3DD42543FB1A1A16897224F0C25E”);
    #gnupg_addencryptkey($res,”61EC B598 C01B 3DD4 2543 FB1A 1A16 8972 24F0 C25E”);
    #gnupg_addencryptkey($res,”5C798B98314176C041DD66324A83C80EF1817BFB”);
    $enc = gnupg_encrypt($res, “just a test”);
    echo $enc;
    echo getenv(“GNUPGHOME”). “\n”;
    ?>

    [root@mirage enrollment]# ls -al /var/www/.gnupg
    total 32
    drwxrwxrwx 2 apache apache 4096 Oct 29 11:39 .
    drwxr-xr-x 11 root root 4096 Oct 28 20:42 ..
    -rwxrwxrwx 1 apache apache 1155 Oct 28 20:47 pubring.gpg
    -rwxrwxrwx 1 apache apache 1155 Oct 28 20:47 pubring.gpg~
    -rwxrwxrwx 1 apache apache 600 Oct 28 20:47 random_seed
    -rwxrwxrwx 1 apache apache 1304 Oct 28 20:47 secring.gpg
    -rwxrwxrwx 1 apache apache 1280 Oct 28 20:47 trustdb.gpg
    [root@mirage enrollment]# php -m | grep gnugp
    [root@mirage enrollment]# php -m

    [root@mirage enrollment]# php -m | grep gnupg
    gnupg

    The GnuPG php module is loaded, permissions are wide open, and I’ve triple checked the key fingerprints… Any idea what I’m doing wrong??? Many thanks your help.

  6. @Troy,

    Permissions may be too open. GPG will complain if your secret key is publicly readable. Try setting everything to be owned by apache, with 600 permissions.

  7. chmod -R 600 /var/www/.gnupg

    [root@mirage enrollment]# php encrypt.php
    PHP Warning: gnupg_addencryptkey(): get_key failed in /var/www/vhosts/personalizedprevention.com/httpdocs/enrollment/encrypt.php on line 10
    PHP Warning: gnupg_encrypt(): no key for encryption set in /var/www/vhosts/personalizedprevention.com/httpdocs/enrollment/encrypt.php on line 13
    /var/www/.gnupg
    [root@mirage enrollment]# php test.php
    PHP Warning: require_once(Crypt/GPG/VerifyStatusHandler.php): failed to open stream: No such file or directory in /usr/share/pear/Crypt/GPG.php on line 61
    PHP Fatal error: require_once(): Failed opening required ‘Crypt/GPG/VerifyStatusHandler.php’ (include_path=’.:’) in /usr/share/pear/Crypt/GPG.php on line 61

    Thanks Brandon, same result though.

  8. Hi Brandon,

    thx for this nice post. It was very useful for me.

    I found 2 errors in Your Application code:

    on Line 9
    dont use $encrypted = $this->gpg->encrypt($data);
    use $encrypted = $gpg->encrypt($data);

    on Line 21 change gnugp_fingerprint to gnupg_fingerprint

    Greetings from Germany, Sven

  9. Hello,

    I have problems with decrypt in php. With command line i haven’t any problems.

    I don’t know what to do. I always got decrypt failed error.

  10. @Bartek, if it works from the command line, but not when accessed through the web server, my guess would be some permissions problem. Try watching the web server’s error log for indications of the failure. Make sure that the user which the web server runs under has permission to read the private key file and write to the temporary directory.

  11. This helped us a lot. Thank you for all the great work and also some of your inputs in the posts too.

    Thank you,
    Atul

  12. Thank you for this post! Had to apply fixes mentioned in the comments, but due to some permissions issues I’ve been trying to gnupg-php work for hours and this post solved it for me.

  13. Thanks for the tutorial, Brandon!

    I can’t seem to figure out why, but the decrypted text is showing up blank for me. The encrypted text shows up just fine – but there is nothing after “Decrypted Text”. There aren’t any errors either. Any ideas about what could be happening or tips on how to troubleshoot some more? (Been looking high and low for any possible reason for the issue I’m facing.

    Thanks!

  14. I changed the way errors are handled and now I am showing a “get_key failed” error. So this would be a permission issue correct? I’m not TOO savvy working in shell, so how would I go about checking permissions (and on what folders/files) and then change them as I need to? Any guidance is appreciated!

  15. Hi All ,

    I got error while page running from web browser. Please check below error :

    Crypt_GPG_FileException: Error reading GnuPG data file ‘/var/www/.gnupg/trustdb.gpg’. Check to make sure it is readable by the current user. in /var/www/testCrypt/GPG.php on line 454.

    We have configuration Server Ubuntu : 12.04 ,PHP 5.3. also set the permission to /var/www/.gnupg check below :

    dwr——- 2 root root .gnupg

    I have checked the web user is like www-data.

    Could somebody help me on this ?

    Thanks,
    Swapnil

Leave a Reply

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