Using GnuPG with PHP

Mission Impossible

One of my favourite television serials of all time has to be “Mission: Impossible”. And my favourite bit of each episode was always the beginning, when Jim Phelps would meet his handler, say a code phrase, and receive an innocuous object that was actually an ingeniously-designed briefing book containing details of his latest mission. My second-favourite bit of each episode? When said innocuous object self-destructed in a puff of smoke in three…two…one…bzzt!

It’s this fascination with gadgets and security that first brought GnuPG to my attention a few years ago. GnuPG is open-source software that makes it possible to encrypt your email and other messages so that they can only be read by the intended recipient (not unlike Jim Phelps and his briefing book). Unlike cleartext messages, which are unprotected and can be read by anyone who intercepts them, GnuPG-encrypted messages are “locked” through cryptographic techniques and may be viewed only by a person with the correct “key”; everyone else will merely see gibberish. In today’s day and age, when hacking and phishing is common, such encryption plays an important role in protecting sensitive data like user names, passwords or credit-card information.

Now, while GnuPG works very well as a standalone tool, it also plays very well with PHP. This integration is possible due to PHP’s ext/gnupg extension, which provides a flexible and powerful API to access GnuPG functions for encryption, decryption, message signing and verification, and key maintenance. And your mission (should you choose to accept it) will be to accompany me over the next few pages, while I give you a crash course in this API, showing you how easy it is to integrate these functions into your next PHP application.

Three…two…one…bzzt!

Locks and Keys

Before we begin, a few quick words about the GNU Privacy Guard, aka GnuPG. According to its official Web site at http://www.gnupg.org/, GnuPG is “GNU’s tool for secure communication and data storage…it can be used to encrypt data and to create digital signatures”. It’s extremely popular in the Internet community as a tool for data protection, because it’s free, offers a high level of security, and is fully compliant with the OpenPGP standard.

To understand how GnuPG works, it’s necessary to also understand the basics of public-key cryptography. Under this cryptographic system, every user holds a pair of keys: a public key, and a private key. The public key may be distributed to all and sundry, and should be world-readable. The private key is not readable by anyone but the owner, and is protected with a passphrase known only to the owner.

To generate an encrypted message, a sender must encrypt the plaintext message using the recipient’s public key (which, as explained above, is easily available). This encrypted message, or ciphertext, is only readable by the recipient, because only the recipient possesses the other half of the key pair – the private key – necessary to decrypt the message. The sender may also optionally “sign” the message using his or her private key; this digital signature can be verified against the sender’s public key should the recipient wish to confirm the sender’s identify in a “reverse check”.

The secure nature of this system arises from two key facts:

Data encrypted with a public key can only be decrypted by the corresponding private key.

It is not possible to deduce a private key from the public key.

Following from the above, it should be clear that private keys are extremely important to the security of the system, and they should be carefully held; a compromised private key can render all of a user’s secure transmissions vulnerable. Public keys, on the other hand, may be freely distributed, either over email or via specialized “public key servers”.

If you’ve understood all of the above, you should now know enough to get through the rest of this article. Or, if you’d like a more detailed overview of how public-key cryptography works, take a look at Chapter 2 of the GnuPG manual, at http://www.gnupg.org/gph/en/manual.html, and then come back and flip the page.

Putting The Pieces Together

Now that you’ve understood the basics of GnuPG, let’s look at how to interact with it through PHP. GnuPG support in PHP comes through PECL’s ext/gnupg extension, which is maintained by Thilo Raufeisen and provides an API for encryption, decryption, signing, and key management. This ext/gnupg extension, in turn, depends on the two GnuPG libraries, libgpg-error and gpgme, which must be compiled and installed to your development environment before ext/gnupg can be built.

Note that at the current time, a Windows version of ext/gnupg is not available; the following steps assume a *NIX system. They also assume that the main GnuPG program is already installed and available; if this is not the case, you can get it (in both source and binary form) from the GnuPG Web site, at http://www.gnupg.org/download/index.en.html. Be sure to use GnuPG 1.3.0 or better.

The first step, then, is to download, compile and install the libgpg-error and gpgme libraries. Visit http://www.gnupg.org/download/index.en.html, download the source code archives and install them using the standard configure-make-make install cycle:

1

2

3

4

5

6

7

8

9

shell# cd libgpg-error-1.6

shell# ./configure

shell# make

shell# make install

shell# cd gpgme-1.1.4

shell# ./configure

shell# make

shell# make install

Once both these libraries are installed, proceed to download ext/gnupg (v1.3.1 at this time) from http://pecl.php.net/package/gnupg, and compile it into a loadable PHP module:

1

2

3

4

shell# cd gnupg-1.3.1

shell# phpize

shell# ./configure

shell# make

At this point, you should have a loadable PHP module named gnupg.so in your ./modules directory. Copy this to your PHP extension directory, and enable the extension in the php.ini configuration file. Restart your Web server, and check that the extension is enabled with a quick call to phpinfo():

Hit List

Once you’ve got the pieces working, it’s time to get started with some code. Let’s begin with something simple – listing all the public keys in your keyring. If you were do this at the command-line, here’s an example of what you’d see:

1

2

3

4

5

6

7

pub1024D/A15F27EA2008-03-07

uidKWoot<kaywoo@example.com>

sub2048g/914AB2192008-03-07

pub1024D/101407222008-03-07

uidDGardner<dgar@example.org>

sub2048g/9EE27DF62008-03-07

Now, how about doing the same thing through a PHP script? Here’s the code:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

&lt;?php

// set path to keyring directory

putenv('GNUPGHOME=/home/sender/.gnupg');

// create new GnuPG object

$gpg=newgnupg();

// throw exception if error occurs

$gpg->seterrormode(gnupg::ERROR_EXCEPTION);

// get list of keys containing string 'example'

try{

$keys=$gpg->keyinfo('example');

print_r($info);

}catch(Exception$e){

echo'ERROR: '.$e->getMessage();

}

?>

This script begins by initializing an instance of the gnupg class, and then calling the instance’s seterrormode() method to ensure that errors, if any, are thrown as exceptions. Next, the instance’s keyinfo() method is called with a search string as argument, and any matching keys are returned as an array. Here’s an example of what the output array looks like:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

Array

(

[0]=>Array

(

[disabled]=>

[expired]=>

[revoked]=>

[is_secret]=>

[can_sign]=>1

[can_encrypt]=>1

[uids]=>Array

(

[0]=>Array

(

[name]=>KWoot

[comment]=>

[email]=>kaywoo@example.com

[uid]=>KWoot&lt;kaywoo@example.com>

[revoked]=>

[invalid]=>

)

)

[subkeys]=>Array

(

[0]=>Array

(

[fingerprint]=>DE735462998845EEA8922D04BAA7F2B0A15F27EA

[keyid]=>BAA7F2B0A15F27EA

[timestamp]=>1204873157

[expires]=>0

[is_secret]=>

[invalid]=>

[can_encrypt]=>

[can_sign]=>1

[disabled]=>

[expired]=>

[revoked]=>

)

[1]=>Array

(

[fingerprint]=>1EF9FE52A0104584F6C4040F9DC6E96A914AB219

[keyid]=>9DC6E96A914AB219

[timestamp]=>1204873161

[expires]=>0

[is_secret]=>

[invalid]=>

[can_encrypt]=>1

[can_sign]=>

[disabled]=>

[expired]=>

[revoked]=>

)

)

)

[1]=>Array

(

[disabled]=>

[expired]=>

[revoked]=>

[is_secret]=>

[can_sign]=>1

[can_encrypt]=>1

[uids]=>Array

(

[0]=>Array

(

[name]=>DGardner

[comment]=>

[email]=>dgar@example.org

[uid]=>DGardner&lt;dgar@example.org>

[revoked]=>

[invalid]=>

)

)

[subkeys]=>Array

(

...

)

)

)

It’s easy to format the output of keyinfo() into something more readable:

Notice the call to putenv() at the top of both these scripts. This creates an environment variable named $GNUPGHOME and sets it to the location of the GnuPG keyring directory; this variable is necessary for ext/gnupg to locate the keyring file to use. Remember to ensure that the user owning the Web server process has read and write privileges to this directory, or else the call to keyinfo() will return an empty array.

Hiding In Plain Sight

The ext/gnupg extension makes it quite easy to encrypt a message using a recipient’s public key from your keyring. To illustrate, consider the following script, which encrypts a message using the public key for ‘dgar@example.org’ and displays the result:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

&lt;?php

// set path to keyring directory

putenv('GNUPGHOME=/home/sender/.gnupg');

// create new GnuPG object

$gpg=newgnupg();

// throw exception if error occurs

$gpg->seterrormode(gnupg::ERROR_EXCEPTION);

// recipient's email address

$recipient='dgar@example.org';

// plaintext message

$plaintext=

"Dear Dave,\n

The answer is 42.\n

John";

// find key matching email address

// encrypt plaintext message

// display and also write to file

try{

$gpg->addencryptkey($recipient);

$ciphertext=$gpg->encrypt($plaintext);

echo'&lt;pre>'.$ciphertext.'&lt;/pre>';

file_put_contents('/tmp/ciphertext.gpg',$ciphertext);

}catch(Exception$e){

die('ERROR: '.$e->getMessage());

}

?>

This script illustrates how a GnuPG public key can be used to encrypt a message using PHP. First, the recipient’s public key is registered with the gnupg object via the object’s addencryptkey() method; this method accepts a unique identifier, such as the recipient’s name, email address or key fingerprint, as argument and looks up the keyring to identify the corresponding key. Then, the object’s encrypt() method is used to encrypt the message and generate a ciphertext version. This ciphertext may then be written to a file with file_put_contents(), emailed to the recipient with mail(), or simply sent to the output device with echo. If the recipient’s key cannot be uniquely identified with the identifier provided, an exception is generated.

By default, the ciphertext generated by the encrypt() method is in ASCII form, such that it can be transmitted or displayed without data corruption However, GnuPG also supports generating ciphertext in binary form; this may be done by calling the gnupg object’s setarmor() method with argument 0.

Here’s what the ciphertext generated by the script might look like:

What if you want to do the reverse – convert an encrypted message back into human-readable form? Well, assuming you’re the intended recipient of the message and have your secret key on your keyring, the procedure is fairly painless. Take a look:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

&lt;?php

// set path to keyring directory

putenv('GNUPGHOME=/home/recipient/.gnupg');

// create new GnuPG object

$gpg=newgnupg();

// throw exception if error occurs

$gpg->seterrormode(gnupg::ERROR_EXCEPTION);

// recipient's email address

$recipient='dgar@example.org';

// ciphertext message

$ciphertext=file_get_contents('/tmp/ciphertext.gpg');

// register secret key by providing passphrase

// decrypt ciphertext with secret key

// display plaintext message

try{

$gpg->adddecryptkey($recipient,'guessme');

$plaintext=$gpg->decrypt($ciphertext);

echo'&lt;pre>'.$plaintext.'&lt;/pre>';

}catch(Exception$e){

die('ERROR: '.$e->getMessage());

}

?>

Here, the adddecryptkey() method is first used to identify the recipient’s secret key on the keyring. This adddecryptkey() method accepts two arguments: a unique identifier for the secret key, and the passphrase to access it. Next, the ciphertext is retrieved from the file to which it was saved earlier, and the decrypt() method is used to decode it and return the original unencrypted message as output.

Here’s what the output of the script will be:

Reading The Signs

GnuPG also lets senders attach secure digital signatures to their messages; recipients can use these to verify the sender’s identity. With ext/gnupg, you can do this programmatically, by using the addsignkey() and encryptsign() methods. Here’s an example:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

&lt;?php

// set path to keyring directory

putenv('GNUPGHOME=/home/sender/.gnupg');

// create new GnuPG object

$gpg=newgnupg();

// throw exception if error occurs

$gpg->seterrormode(gnupg::ERROR_EXCEPTION);

// recipient's email address (for encryption key)

$recipient='dgar@example.org';

// sender's email address (for signing key)

$sender='kaywoo@example.com';

// plaintext message

$plaintext=

"Dear Dave,\n

The answer is 42.\n

John";

// find key matching email address

// encrypt plaintext message with recipient public key

// sign plaintext message with sender private key

// display and also write to file

try{

$gpg->addencryptkey($recipient);

$gpg->addsignkey($sender,'guessme');

$ciphertext=$gpg->encryptsign($plaintext);

echo'&lt;pre>'.$ciphertext.'&lt;/pre>';

file_put_contents('/tmp/ciphertext.gpg',$ciphertext);

}catch(Exception$e){

die('ERROR: '.$e->getMessage());

}

?>

This is similar to the hoops you jumped through earlier, when encrypting a message with the recipient’s public key. However, there are two additional steps to sign a message: first, register the sender’s private key by calling the addsignkey() method with an identifier and the passphrase for the private key and then, encrypt and sign the message in one swell foop with the encryptsign() method. The resulting output is a GnuPG-encrypted message block that also contains the sender’s signature.

When the recipient receives the message, he or she can decrypt it in the normal fashion, by registering the decryption key using adddecryptkey() and the key password and then calling the decrypt() method. This is, in fact, the method shown on the previous page. However, there’s also another way to do this: by replacing the call to decrypt() with a call to decryptverify(), you can perform the dual tasks of decryption and signature verification in a single step. Here’s an illustration:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

&lt;?php

// set path to keyring directory

putenv('GNUPGHOME=/home/recipient/.gnupg');

// create new GnuPG object

$gpg=newgnupg();

// throw exception if error occurs

$gpg->seterrormode(gnupg::ERROR_EXCEPTION);

// recipient's email address

$recipient='dgar@example.org';

// ciphertext message

$ciphertext=file_get_contents('/tmp/ciphertext.gpg');

// register secret key by providing passphrase

// decrypt ciphertext with private key

// verify signature using public key of sender

// display plaintext message and result of signature verification

try{

$gpg->adddecryptkey($recipient,'guessme');

$retVal=$gpg->decryptverify($ciphertext,&$plaintext);

echo'&lt;pre>'.$plaintext.'&lt;/pre>';

if(isset($retVal[0]['fingerprint'])){

$fp=$retVal[0]['fingerprint'];

echo'Signed by key: '.$fp;

}

}catch(Exception$e){

die('ERROR: '.$e->getMessage());

}

?>

And here’s what the output might look like:

The decryptverify() method accepts two arguments: the encrypted message block, and a reference to the variable in which to store the decrypted message. If the message includes a signature, the method returns an array containing information on the key used to sign the message. Here’s what the array looks like:

1

2

3

4

5

6

7

8

9

10

11

Array

(

[0]=>Array

(

[fingerprint]=>DE735462998845EEA8922D04BAA7F2B0A15F27EA

[validity]=>0

[timestamp]=>1205137410

[status]=>0

[summary]=>0

)

)

It’s now not very difficult to retrieve the sender’s name and email address: simply use the key fingerprint with the keyinfo() method to find the corresponding public key on your keyring.

Incidentally, the encryptsign() and decryptverify() methods are two-in-one methods: they respectively sign-and-encrypt and verify-and-decrypt. If this is too much excitement for you, you can sign a message (without encrypting it) and verify a signature (without decrypting the message it’s attached to) with the simpler sign() and verify() methods.

Making New Friends

It’s also possible to use a PHP script to import and export keys from a GnuPG keyring. The relevant ext/gnupg methods are called – surprise surprise! – import() and export() respectively. Here’s an example of exporting a key to a file, using the owner’s email address to locate it on the keyring:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

&lt;?php

// set path to keyring directory

putenv('GNUPGHOME=/home/sender/.gnupg');

// create new GnuPG object

$gpg=newgnupg();

// throw exception if error occurs

$gpg->seterrormode(gnupg::ERROR_EXCEPTION);

// key identifier

$me='kaywoo@example.com';

// export public key matching identifier

// display and save to file

try{

$key=$gpg->export($me);

echo'&lt;pre>'.$key.'&lt;/pre>';

file_put_contents('/tmp/key.out',$key);

}catch(Exception$e){

die('ERROR: '.$e->getMessage());

}

?>

Importing a key is just as simple: call the import() method with the key data as argument. Here’s an example:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

&lt;?php

// set path to keyring directory

putenv('GNUPGHOME=/home/sender/.gnupg');

// create new GnuPG object

$gpg=newgnupg();

// throw exception if error occurs

$gpg->seterrormode(gnupg::ERROR_EXCEPTION);

// import public key from file

// display results

try{

$key=file_get_contents('/tmp/key.out');

$retVal=$gpg->import($key);

echo$retVal['imported'].' key(s) imported.';

}catch(Exception$e){

die('ERROR: '.$e->getMessage());

}

?>

The output of the import() method is an array containing a report of what happened during the import process. Here’s what the array looks like (notice the ‘imported’ key, which tells you how many keys were actually imported to the destination keyring):

1

2

3

4

5

6

7

8

9

10

11

12

Array

(

[imported]=>1

[unchanged]=>0

[newuserids]=>0

[newsubkeys]=>0

[secretimported]=>0

[secretunchanged]=>0

[newsignatures]=>0

[skippedkeys]=>0

[fingerprint]=>DE735462998845EEA8922D04BAA7F2B0A15F27EA

)

End Credits

The various functions exposed by ext/gnupg are sufficient to enable some fairly useful applications. To illustrate, consider the next example, which demonstrates how to build a PHP application to interactively encrypt, sign and email a message to a recipient listed in your GnuPG keyring:

This might look intimidating, but it’s actually pretty simple. The script begins by instantiating a gnupg object, and defining the path to the user’s keyring. It then splits into two sections, with the first section responsible for generating a Web form and the second for processing the form input.

The first half of the script generates a Web form, with fields for the user’s name, email address, passphrase (only needed for signed messages) and message. There’s also a list of recipients that the message may be sent to; this is dynamically built from the user’s keyring, by iterating over the data returned by the keyinfo() method. Here’s what the form might look like:

Once the form is submitted, the second half of the script comes into play. After validating the input values, the script locates the selected recipient’s public key in the keyring, and registers it using the addencryptkey() method. Next, the script checks if a passphrase was entered; if it was, it implies that the user wishes the message to be signed, and so the addsignkey() method is used to register the signing key. The message is then encrypted and, optionally, signed using either the encrypt() or encryptsign() methods, and the message is sent to the email address listed in the recipient’s public key using PHP’s mail() function. An error at any stage – for example, an incorrect passphrase or an invalid key identifier – is caught by the exception handler and displayed to the user.

As this example illustrates, PHP’s GnuPG extension makes it fairly easy to bring the power and security of GnuPG into your PHP application. Try it out for yourself the next time you have some data to protect…and happy encrypting!