soupslurpr/AppVerifier

SHA-256 hashes in AppVerifier database vs 40 character SHA-1 GPG public key hashes

aChrisYouKnow opened this issue · 9 comments

Pardon the potentially ignorant question(s), but it seems relatively easy to find the 40 character SHA-1 GPG public key of developer's signing keys, for example, Mullvad's signing key: A1198702FC3E0A09A9AE5B75D5A1D4F266DE8DDF found here: https://mullvad.net/en/help/verifying-signatures and many other places such as twitter (https://twitter.com/mullvadnet/status/794122723781918720). Where do the SHA-256 hashes come from that can be found in AppVerifier's internal database? I see three different hashes for Mullvad; one for the website/github APK, one for the google Play Store APK, and one for the F-Droid APK. When I try searching for these 64 digit SHA-256 hashes, I can't any other references to them, which makes them harder to verify. When Mullvad releases a signature with each release, they are signing it with their A1198702FC3E0A09A9AE5B75D5A1D4F266DE8DDF signing key. Presumably this isn't the same key that is signing the APK certificates, and there are three different keys being used for the different app stores. My questions are:

  1. What are the benefits of making a database of the SHA-256 hashes of public keys that sign the APK certificates vs comparing to the SHA-1 GPG public key of valid APK signatures made available from the developer?
  2. Any recommendations for how to find these 64 digits SHA-256 public keys if they're not already in the AppVerifier database? I suppose download the APK file on desktop, verify the GPG signature of the signed APK, then use apksigner to view the certificate. Seems much more involved than searching other public locations.
  3. The above likely contains inaccurate assumptions. I appreciate any corrections.

I went looking for an open source way to verify signatures of APK files on mobile, and this seems to be designed to do that. Just curious as to the reasons behind this methodology vs what I'm more familiar with.

Appreciate it!

On Android, apps must be signed. Every app is signed so we can get the hash of the signing certificate(s) and verify using that. I'm not sure what the GPG key that Mullvad posts for their Android app is. But whatever it is, most apps don't do that.

For finding the SHA-256 hashes of the signing certificate(s), you could ask the developers to post them. Downloading the apk file on the desktop won't do anything compared to using AppVerifier on the Android device. You could also verify with other people, by sharing the verification info to them and receiving theirs and verifying that they are the same (just share the verification info to AppVerifier or use verify from clipboard). Hope that helps!

Downloading the apk file on the desktop won't do anything compared to using AppVerifier on the Android device.

I was mentioning this as a way to get the APK certificate signature from another device to compare, if it wasn't already in AppVerifier's internal database, and wasn't found when searching online. At least you could have a second result for comparison if you wanted to rule out a specific device or a specific network being compromised.

most apps don't do that.

This isn't my experience. Well, fair, maybe not most, but, a larger number do. At least for apps that cater to the security conscious. The most common verification methods seem to be either signing the APK with the developer’s signing key and posting the signature where the APK file can be downloaded (Mullvad as an example: https://github.com/mullvad/mullvadvpn-app/releases/) or signing the SHA256 hash of the APK file (Obtanium as an example https://github.com/ImranR98/Obtainium/releases ).

Some developers just post SHA256 hashes of the APK in their releases, such as Bitwarden, Cryptomator, and Standard Notes:
https://github.com/bitwarden/mobile/releases/
https://github.com/cryptomator/android/releases
https://github.com/standardnotes/app/releases

Just posting the SHA256 hash is still better than nothing as it still does mitigate a number of attack vectors, but, it would certainly be better if more developers provided a signature that could be traced back to a public key that has been posted in multiple locations.

All this said, I love the purpose of AppVerifier; to be able to compare the signature of a specific APK file with a second source (in this case, a database maintained by someone other than the developer). I am just trying to wrap my head around the pros and cons over pushing for more developers to include signed APKs on their GitHub repo.

It seems the pros and cons of AppVerifier’s method vs GPG signing are:

Pros:

  • Can check the signature of the currently installed app, not just the APK file pre-install
  • All android apps already need to be signed, so no convincing developers to comply
  • You've already built a really slick app to do this. I found mostly closed source apps to verify GPG signatures of files on mobile, or more clunky solutions using terminal emulators

Cons:

  • Even from one developer, different app versions (Play Store vs F-Droid vs other build options) are likely to have different signers. See Mullvad's three hashes AppVerifier’s database. This means more public key hashes per developer, so less mentions, and harder to find from an SEO standpoint.
  • Ideally the developers would post their public key hashes, so you’re back to convincing developers to participate. Otherwise you’re stuck with comparing them between other users (which is still much better than nothing)
  • As of now, these hashes are much less commonly found via web searches. People seem to mention GPG keys for developers far more than android signing keys for developers.

I'm certainly down to start asking around for more developers to post their android certificate signing keys places. Just wanted to try to better understand the pros/cons of this over GPG signatures first. Also, nomenclature-wise, is 'SHA256 hash of your android certificate public signing key' what I'd be asking for?

Thanks for the hard work!

I think the right words would be SHA-256 hash(es) of the app's signing certificate(s).

One problem with publishing apk hashes is that they change every release. The signing certificate hashes don't change every release, but it can be rotated which means a newer signing certificate hash would be added but the old one can still be verified against.

One problem with publishing apk hashes is that they change every release.

Most of the time the apk hashes are included, is because a short text file of the hash is signed with the constant GPG signature. So the constant is the signature of the hash, not the hash itself.

However, in the examples of Bitwarden, Cryptomator, and Standard Notes, they do only post the hash for some reason. Which as you said, changes each release. You can still usually find them with a search though, at at least verify someone else got the same hash, and that the hash you're getting is the same as the hash from GitHub, ruling out any local man in the middle attack type scenario.

I think the right words would be SHA-256 hash(es) of the app's signing certificate(s).

Thanks.

Anything else I'm missing in the pros and cons above if the comparison is between:
A) An app that checks an APK file against a database of SHA-256 hashes of signing certificates (i.e. AppVerifier)
B) An app that prompts you to select both an APK file, as well as a signature file (both downloaded from GitHub for example). The app then checks to confirm the signature is valid. The signature could either be of the APK itself, or of the SHA256 hash of the APK file (both cases could be checked against for an easier UX). The database would still be needed, which would be a database of GPG public keys. These are much easier to find though.

(A) definitely seems more straightforward, and hopefully will become more common than (B), which seems to mostly rely on desktop verification of APK files.

I'm not knowledgeable enough in GPG keys to say much about it but in general its probably better to verify the actual signing certificate the app is signed with rather than a detached file.

If you sign the APK file, or sign the hash of a APK file, any change at all will cryptographically invalidate the signature. The signature file being detached doesn't change that. There are very well known GPG key repositories (https://keyserver.ubuntu.com, https://pgp.mit.edu, https://keys.openpgp.org and many more more) that you can already find developers signing keys, and have them verified in multiple different places. Starting a new database of signing certificates when there is such an established network of public key repositories seems like it would require some strong advantages.

Maybe verifying the signature of an APK on mobile is too computationally intensive? And therefore comparing the certificate to a database is the way to go? I assume there is some reason that it is not more common practice to verify APK signatures on mobile or there would be more apps out there that do it, seeing as how that is the standard method for verifying the authenticity of files in a desktop environment, I just don't know what the reason is. Anyway, this initial issue was trying to understand that, but, my intent isn't to waste this much of your time (though I have enjoyed the discussion). Feel free to close this issue out whenever.

The apk hash changes every apk while the signing certificate hash is the same. Its better to have one verification info for all the releases even future ones (unless key rotation occurs, in which case the old cert will still be valid, just a new one will be added.

The public PGP/GPG key that is doing the signing is the constant, just as you are using the public key from the certificates as the constant. All future and past releases use the same key to sign the certificate, just as all future and past releases are being signed by the same public PGP key. The aspect of having some constant to compare to in a public database is the same for both methods. In both cases, if the APK file changes, the certificate (or signature) is provably no longer valid because it doesn't match the file. This part doesn't appear to be different at all between methods.

Ah I see. Okay