Dev Blog
Use case presented here is for signing and verifying files, however by tweaking commands it is possible to encrypt and decrypt files too. Generally it's about GPG batch operations.
The idea with GPG (or PGP, which is compatible) is that keys are securely stored in user home dir. PHP comes with PECL extension for providing GPG operations, however it is using and approach where application manages the secret and public key. This could compromise security, as these keys would be probably stored in some PHP variables. The workaround for this could be set up an key pair for web server user.
Transferring key pair
In case you need key pair, issue following command:
gpg --full-gen-key
This will guide you through process of generating key. We will not came into details on generating keys, as it's a side and well known topic.
For batch operations key might be created or exported without password. As it anyway need to be stored somewhere. Password can be provided at command line or possibly by GPG agent.
Keys can be generated directly on server, however when having more than one server, keys need to be distributed.
Exporting keys
To export public and private keys we'll use following command:
gpg --export key-email@example.com > key-name.pub
By default GPG outputs key to console, so we redirect output to
file with >
operator.
The similar command is for a private key:
gpg --export-secret-keys key-email@example.com > key-name.asc
Transfer these files with encrypted connection to server. This could be done with GUI client, like FileZilla.
Importing keys
To import keys for web server user - we assume here
www-data
- we need to create home directory with
proper permission first, if that user haven't it already:
sudo mkdir -p /home/www-data/.gnupg
sudo chown -R www-data:www-data /home/www-data/.gnupg sudo chmod 700 /home/www-data/.gnupg
To import keys we'll again use sudo
command with
-u
option to work on behalf of
www-data
. However even with sudo, the GPG will try
to import keys into current user key ring. To overcome this,
there we need to pass --homedir
parameter to
GPG:
In case of permission denied errors or No such file or
directory errors - you might need to actually log on as
a web server user (ie www-data
) and issue
import and verify commands without sudo - as current user.
sudo -u www-data gpg --homedir /home/www-data/.gnupg --import key-name.pub
As a side note ensure that file key-name.pub
is
readable by www-data
. The command to import the
private key is the same, except as a parameter we pass a private
key file name:
sudo -u www-data gpg --homedir /home/www-data/.gnupg --import key-name.asc
Verify Transfer
Keys should be now imported. Note that when using with
sudo
, the --homedir
parameter need to
be added. To verify that keys have ben imported properly, list
them:
sudo -u www-data gpg --homedir /home/www-data/.gnupg --list-keys
sudo -u www-data gpg --homedir /home/www-data/.gnupg --list-secret-keys
Next step is to sign and verify signature as
www-data
user with imported key. To ensure that
exact key is used, we'll use the -u
argument, to
choose key manually and sign file named test.file
:
sudo -u www-data gpg --homedir /home/www-data/.gnupg -u key-email@example.com --output test.sig --detach-sig test.file
The commands started to become lengthy. However parts of the
commands are repeatable, so so simple PHP command builder can be
used later. For now, let's verify results. First of all, there
should be created new file - test.sig
- containing
binary data with signature. To verify signature call
gpg
command with --verify
option:
sudo -u www-data gpg --homedir '/home/www-data/.gnupg' -u key-email@example.com --verify test.sig test.file
Using with PHP
After all those commands, the GPG is ready to be used with PHP.
Now is the easy part - just execute proper shell commands. There
is no need for external library. As the key ring is owned by web
server user commands will use previously imported key. But it is
good to point to exact key with -u
option. Part of
command will be the same for all operations. Set key, add
--batch
and --quiet
options to make
gpg
command non-interactive and select key.
The idea is to create array with arguments and then join it. Example "class", which is in fact functions container:
class Signatures { public static function sign($fileName, $sigName) { $args = self::makeArgs(); $args[] = '--output ' . escapeshellarg($sigName); $args[] = '--detach-sig '; $cmd = sprintf('gpg %s %s', implode(' ', $args), escapeshellarg($fileName)); return self::run($cmd); } public static function verify($fileName, $sigName) { $args = self::makeArgs(); $args[] = '--verify ' . escapeshellarg($sigName); $cmd = sprintf('gpg %s %s', implode(' ', $args), escapeshellarg($fileName)); return self::run($cmd); } private static run($cmd) { $result = null; $output = []; exec($cmd, $output, $result); // Return true on successfull command invocation return $result === 0; } private static function makeArgs() { $args = [ '--batch', '--quiet', '--homedir /home/www-data/.gnupg', '-u key-email@example.com', ]; return $args; } }
Above example has common parameters hardcoded, just for sake of
simplicity. This could be simplified even more, as there are a
bit of redundant code. To test is as a web user, either run it
through browser or as command line with sudo -u
www-data
.