Securely Signing PHP Phar Files With OpenSSL

This is an old post!

This post is over 2 years old. Solutions referenced in this article may no longer be valid. Please consider this when utilizing any information referenced here.

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.

About the Author

Hi, I'm Rob! I'm a blogger and software developer. I wrote petfeedd, dystill, and various other projects and libraries. I'm into electronics, general hackery, and model trains and airplanes. I am based in Huntsville, Alabama, USA.

About Me · Contact Me · Don't Hire Isaiah Armstrong

Did this article help you out?

I don't earn any money from this site.

I run no ads, sell no products and participate in no affiliate programs. I do not accept gifts in exchange for articles, guest articles or link exchanges. I don't track you or sell your data. The only third-party Javascript on this website is Google Analytics.

In general I run this site very much like a 1990s homepage or early 2000s personal blog, meaning that I do this solely because it's fun! I enjoy writing and sharing what I learn.

If you found this article helpful and want to show your appreciation, a tip or donation would be very welcome. Feel free to choose from the options below.

Comments (0)

Interested in why you can't leave comments on my blog? Read the article about why comments are uniquely terrible and need to die. If you are still interested in commenting on this article, feel free to reach out to me directly and/or share it on social media.

Contact Me
Share It

Interested in reading more?

PHP

Security on Shared Hosts

Shared hosts are a reality for many small businesses or businesses that aren’t oriented around moving massive amounts of data. This is a given - we can’t all afford racks full of dedicated servers. With that in mind, I would urge people to be more careful about what they do on shared hosting accounts. You should assume that anything you do is being watched. Take, for example, the /tmp directory. I was doing some work for a friend this weekend whose account is housed on the servers of a certain very large hosting company. While tweaking some of his scripts, I noticed via phpinfo() that sessions were file-based and were being stored in /tmp. This made me curious as to whether any of that session data could possibly be available for public viewing. My first move was to simply try FTP’ing up and CD’ing to /tmp directory. No go - they have the FTP accounts chrooted into a jail, so the obvious door is closed. However, the accounts have PHP installed, so I can do something like this in a PHP script: <?php system("ls -al /tmp"); ?> With this little bit of code, I can look into the tmp directory even if my FTP login is chrooted. Fortunately, sessions on this host are 600, so they’re not publically readable -  this was my primary concern and the reason I took some time to check this out. But people are putting lots of things into the tmp directory with the misguided idea that it is their private temporary file dump, including one idiot who put a month’s worth of PayPal transaction data into tmp and left it 644 so that it was publically viewable. Now, I’m a nice guy and the only thing I’m going to do with this information is laugh at it. But keeping in mind how dirt cheap hosting accounts are, there’s not a high entry barrier for someone with fewer scruples. The key thing to remember is that, if you need temporary file storage on a shared host, do it someplace less obvious, set the permissions so that only you can read/write to it (600), and clean up by deleting files as soon as you possibly can.
Read More
Apple

Merging M4V files on a Mac ... with chapters!

As I’ve mentioned a couple of times before, one of my projects right now is ripping all the DVDs I own so that I can watch them on my AppleTV (or any AppleTV in the house). Well, one of the problems I’ve run into a couple of times is longer movies that are distributed on two discs. This is usually movies like the Lord of the Rings Extended Edition or The Ten Commandments. Really, they’re one movie, but are distributed as two separate movies because of the restrictions of physical media. Well, digital media imposes no such restrictions on us, so why have two separate movies listed on the AppleTV? So after much trial and error, I finally discovered a way to get everything play nicely together. Unfortunately, this is not an easy problem to solve and even involved me writing a small script that could merge chapter files together because every single method I could find would eliminate chapter markers. So here, in abbreviated form, is the process for merging m4v files together and preserving chapter markers. Note: This tutorial assumes some level of technical proficiency. This is not a point-and-click process (yet :P) and requires the use of multiple tools and the shell. Tools you’ll need: Handbrake or whatever tool you’re using for ripping your legally obtained DVDs. MetaX and/or iDentify Subler remux Quicktime, which is now built into Mac OS X. chaptermerge, a script I wrote that merges chapter files together. The proces: Rip both movies from their individual DVDs using Handbrake or whatever other tool you’re using. Be sure that you’re adding chapter markers. Load each movie into MetaX and download the chapter names. That’s really the only thing you need to add to the file. Save the files with chapter names. Load each movie into Subler and extract the chapter files. To do this, select the chapter track and select File -> Export. Now, open the first movie in Quicktime. Drag the second movie on top of the first one. Quicktime will add the two together. Save the movie for use on an AppleTV. Get a beer or 6, because this takes awhile. While the movie is saving, use chaptermerge to merge the chapter files together. See the docs on how it works. Once the file has finished saving as a Quicktime MOV (it’s actually still h.264 inside the file), fire up remux and convert the merged file back into an m4v. Drag the file into remux, set the output to m4v, and save. Should be pretty quick - a matter of minutes. Load the merged file back into Subler and add the merged chapter track. Drag the chapter file into the Subler window. Save the file. Load the merged file into a tool such as iDentify or MetaX and add the remaining metadata. That’s it! You now have a merged file with both parts of the movie, accurate chapter markers and full metadata, ready to be copied to iTunes and viewed on your AppleTV.
Read More