Securely Signing PHP Phar Files With OpenSSL

By · Published · php, security, phar, openssl, howto

PHP's PHAR archives (PHp ARchives, get it?) are a neat development. They're a way to distribute an entire PHP application as a single archived file that can be executed directly by the PHP intepreter without unarchving them before execution. They're broadly equivalent to Java's JAR files and they're super useful for writing small utilities in PHP.

Starting with PHP 5.3, you can optionally use OpenSSL to sign a Phar archive. The intepreter will now refuse to run the code unless the required public key is present. This provides some security protection; you now know that any subsequent Phars that have been signed with that key are valid updates.

But, unusually, the documentation on Phars, and especially on signing them, are not really all that clear and, frankly, not up to the usual standards of the PHP project. So, here's a quick primer on building a self-signed Phar file.

Big Fat Warning

I am not a security expert. I know a good bit, and I've done a fair bit of research to try to do this the proper way, but I am not warrantying the following information. Moreover, if you see a problem or know of a better way, please contact me and I will update the post.

Prerequisites

  1. PHP of at least 5.3 (really, you should be on at least 5.6 now).
  2. phar.readonly=0 set in php.ini.
  3. The OpenSSL extension installed.
  4. OpenSSL installed on your computer.
  5. A Phar file that you want to sign. Creating a Phar file is beyond the scope of this post.

Step 1: Create a Public/Private Key

Use OpenSSL to create a public/private key pair. This is the fast, but slightly less secure way.

openssl genrsa -out private.pem 4096
openssl rsa -in private.pem -pubout -out public.pem

If you want further security, you could create a key that requires a password to use:

openssl genrsa -des3 -out private.pem 4096
openssl rsa -in private.pem -outform PEM -pubout -out public.pem

The upside of this is it's now even harder to compromise your key, because a potential attacker would need to both have the private key and know the password to use it. The downside of this is that you will need your password every time you use this key, which precludes the use of automated build tools.

Because the small value of the passworded key vs. the big value of using automated build tools, I usually use the first method.

So you now have 2 keys: a public key, and a private one. Store the private one some place safe. If your private key is compromised, there is no real good way to invalidate it once you have your Phars out in the wild. Your users will need to download a new Phar with a new public key if your key is compromised, not to mention the bad things that could happen if someone could sign updates that look like they come from you. So don't let that happen. Keep that key safe.

Step 2: Sign the Phar

Okay, you now have a public/private key, how do you sign the Phar? Well, it turns out it's pretty easy if not terribly well documented. You do it within PHP code!

If you used the first method (the one without passwords), it goes something like this:

<?php
$phar = new Phar("/path/to/your/file.phar");
$private_key = file_get_contents("/path/to/your/private.key");
$phar->setSignatureAlgorithm(Phar::OPENSSL, $private_key);
?>

That's it! It's also worth noting that there's no save() or anything to save the changes to the Phar. The signature is applied when you call the setSignatureAlgorithm() method.

If you used the second method, you have two options. The first option is to temporarily export the passworded private key to an unpassworded one and use the method above (and, certainly, afterward deleting the unpassworded key). To do that, you would issue the following command:

openssl rsa -in private.pem -out private_unencrypted.pem -outform PEM

Then, you would use the first method above. And don't forget to delete that unpassworded key when you're done with it!

The other option is to extract the private key in code. It would go something like this. Also, presumably, you would prompt the developer to enter the password and not save it in code (which would completely defeat the purpose of using passwords to begin with).

<?php
$private = openssl_get_privatekey(file_get_contents('private.pem'));
$private_key = '';
openssl_pkey_export($private, $private_key, $password);
$phar = new Phar("/path/to/your/file.phar");
$phar->setSignatureAlgorithm(Phar::OPENSSL, $private_key);
?>

Though, again, you will need to provde the password to export the key.

Step 3: Distributing Your Phar

So what about the public key? So far we haven't done anything with it, but that's about to change. And this is important, so pay attention here.

The public key must be named the same name as the Phar file, with .pubkey added, and must be in the same directory as your Phar. If your Phar is called myphar.phar, your public key distributed with your phar would be myphar.phar.pubkey. This is in the docs, but it's pretty deeply buried and not very clear - I actually discovered this by reading the relevant PHP C source code after I couldn't figure out why it wasn't working.

If you don't do this, your users will not be able to run your Phar. The PHP engine will refuse to run the code.

So your final distribution would contain the folloing files:

  • file.phar - Your signed Phar archive.
  • file.phar.pubkey - The public key to verify the signed archive against.

For most of my projects that use Phar, I have a Robo task that builds the Phar, signs it, sets it executable, packages it up in a Zip file and deploys the Zip file, the Phar itself and the public key to a distribution server.

In my next post, I'll talk about using this to create user-updatable Phar files that leverage this signing method to insure safety for your users.

( Comments )

Did something I wrote help you out?

That's great! I don't earn any money from this site - I run no ads, sell no products and participate in no affiliate programs. I do this solely because it's fun; I enjoy writing and sharing what I learn.

All the same, if you found this article helpful and want to show your appreciation, here's my Amazon.com wishlist.


Related Posts

Security on Shared Hosts

Extending Homestead: Building A Laravel Dev Environment on Mac OS X


comments powered by Disqus