Signing Project Releases

Fabien Potencier

August 05, 2014

About a year ago, I started to sign all my Open-Source project releases. I briefly mentioned it during my SymfonyCon keynote in Warsaw, but this post is going to give you some more details.

Whenever I release a new version of a project, I sign the Git tag with my PGP key: DD4E C589 15FF 888A 8A3D D898 EB8A A69A 566C 0795.

Checking Git Tag Signatures

If you want to verify a specific release, you need to install PGP first, and then get my PGP key:

$ gpg --keyserver --recv-keys 0xeb8aa69a566c0795

Then, use git tag to check the related tag. Here is how to check the Symfony 2.4.2 tag (from a Symfony clone):

$ git tag -v v2.4.2

Verification worked if the output contains the key used to sign the tag (566C0795) and contains a text starting with "Good signature from ...". Because of how Git works, having a good signature on a tag also means that all commits reachable from that tag are covered by this signature (that's why signing all commits/merges is not needed.)

You can see the PGP signature by using the following command:

$ git show --show-signature v2.4.2

For the curious ones, I'm going to take Symfony 2.4.2 as an example to explain how it works. First, Git does not sign the contents of a commit itself (which is empty anyway for tags), but its headers. Let's display the headers for the Symfony v2.4.2 tag:

$ git cat-file -p v2.4.2

You should get the following output:

object b70633f92ff71ef490af4c17e7ca3f3bf3d0f304
type commit
tag v2.4.2
tagger Fabien Potencier <> 1392233223 +0100
created tag 2.4.2
Version: GnuPG v1.4.13 (Darwin)

The PGP signature is calculated on all lines up to the beginning of the signature:

object b70633f92ff71ef490af4c17e7ca3f3bf3d0f304
type commit
tag v2.4.2
tagger Fabien Potencier <> 1392233223 +0100
created tag 2.4.2

You can try it by yourself by saving those lines in a test file, and create a test.sig file with the PGP signature:

Version: GnuPG v1.4.13 (Darwin)

Then, check that the signature matches the Git headers with the following command:

$ gpg --verify test.asc test

So, when signing a tag, you sign the commit sha1 (and so all reachable commits), but also the tag name (and so the version you expect to get).

Signing Github Archives

That's great, but when using Composer, you can get the code either as a Git clone (--prefer-source) or as an archive (--prefer-dist). If Composer uses the latter, you cannot use the signature coming from the tag, so how can you check the validity of what Composer just downloaded?

Whenever I make a new release, I also publish a file containing a sha1 for the zip file as returned by the Github API ( but also a sha1 calculated on the file contents from the zip (the exact same files installed by Composer.) Those files are hosted on a dedicated checksums repository on Github.

As an example, let's say I have a project using Symfony 2.4.2 (you can check the version installed by Composer by running composer show -i). The sha1s are available here:

This file is signed, so you first need to verify it:

$ curl -O
$ gpg --verify v2.4.2.txt

Now, you can check the validity of the files downloaded and installed by Composer:

$ cd PATH/TO/vendor/symfony/symfony
$ find . -type f -print0 | xargs -0 shasum | shasum

The sha1 displayed should match the one from the file you've just downloaded (the one under the files_sha1 entry.)

To make it easier, you can also check all your dependencies via a simple script provided in the repository. From your project root directory (where the composer.json file is stored), run the following


It will output something along the lines of:

symfony/swiftmailer-bundle@v2.2.6                        OK  files signature
symfony/symfony@v2.5.2                                   KO  files signature
twig/extensions@v1.0.1                                   OK  files signature
twig/twig@v1.15.0                                        OK  files signature
white-october/pagerfanta-bundle@dev-master               --  unknown package
willdurand/hateoas@1.0.x-dev                             --  unknown package
 1 packages are potentially corrupted.
 Check that your did not add/modify/delete some files.

Consider the checksum feature as experimental and as such, any feedbacks would be much appreciated.


gravatar Sarfraz Ahmed  — August 05, 2014 11:07   #1
This sounds great but not sure what's the point of signing projects. Have never done/heard this before in php world. Sounds interesting though for some reason behind it...
gravatar soatok  — August 05, 2014 14:26   #2
Sarfraz: The point of signing projects is to cryptographically assure users that the package they download off the server is an authentic copy of the one provided by the author, and has not been replaced with a trojan horse by a hacker. (See also: )

Fabien: Thanks so much for doing your part to establish good security habits, as well as this blog post. I'll definitely reference this when releasing FurBB :D
gravatar Sarfraz Ahmed  — August 06, 2014 10:51   #3
@soatok: Thanks for the explanation. This is cool stuff then :)
gravatar RĂ©mi Alvado  — August 06, 2014 13:05   #4
It would be great if composer could automatically do this job for us. It would ensure that everyone using symfony as well as composer is deploying a genuine version of the symfony codebase.
gravatar Thomas Koch  — August 07, 2014 13:52   #5
Thank you! This comes at the same time as Sonatype is switching Maven Central to SSL[1]. Maybe I'll see the day when I can have a trusted dev environment? Now please have keysigning sessions at Symfony conferences! Your key has only 3 signatures so far and I can't find a trust path from my key to your key yet.

It's of course much better than nothing and you also wrote your fingerprint in this blogpost. But your site could of course also be hacked and even the https version uses a self signed certificate...

So again: Thank you, but still much ahead!