Browse Source

android reproducibility: add Signal's "apkdiff.py" tool, and instructions

patch-4
SomberNight 4 years ago
parent
commit
45cb8b5b02
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 22
      contrib/android/Readme.md
  2. 78
      contrib/android/apkdiff.py

22
contrib/android/Readme.md

@ -56,6 +56,9 @@ folder.
./contrib/android/make_apk
```
Note: this builds a debug apk. `make_apk` takes an optional parameter
which can be either `release` or `release-unsigned`.
This mounts the project dir inside the container,
and so the modifications will affect it, e.g. `.buildozer` folder
will be created.
@ -63,6 +66,25 @@ folder.
5. The generated binary is in `./bin`.
## Verifying reproducibility and comparing against official binary
Every user can verify that the official binary was created from the source code in this
repository.
1. Build your own binary as described above.
Make sure you don't build in `debug` mode (which is the default!),
instead use either of `release` or `release-unsigned`.
If you build in `release` mode, the apk will be signed, which requires a keystore
that you need to create manually (see source of `make_apk` for an example).
2. Note that the binaries are not going to be byte-for-byte identical, as the official
release is signed by a keystore that only the project maintainers have.
You can use the `apkdiff.py` python script (written by the Signal developers) to compare
the two binaries.
```
$ python3 contrib/android/apkdiff.py Electrum_apk_that_you_built.apk Electrum_apk_official_release.apk
```
This should output `APKs match!`.
## FAQ

78
contrib/android/apkdiff.py

@ -0,0 +1,78 @@
#! /usr/bin/env python3
# from https://github.com/signalapp/Signal-Android/blob/2029ea378f249a70983c1fc3d55b9a63588bc06c/reproducible-builds/apkdiff/apkdiff.py
import sys
from zipfile import ZipFile
class ApkDiff:
IGNORE_FILES = ["META-INF/MANIFEST.MF", "META-INF/CERT.RSA", "META-INF/CERT.SF"]
def compare(self, sourceApk, destinationApk):
sourceZip = ZipFile(sourceApk, 'r')
destinationZip = ZipFile(destinationApk, 'r')
if self.compareManifests(sourceZip, destinationZip) and self.compareEntries(sourceZip, destinationZip) == True:
print("APKs match!")
else:
print("APKs don't match!")
def compareManifests(self, sourceZip, destinationZip):
sourceEntrySortedList = sorted(sourceZip.namelist())
destinationEntrySortedList = sorted(destinationZip.namelist())
for ignoreFile in self.IGNORE_FILES:
while ignoreFile in sourceEntrySortedList: sourceEntrySortedList.remove(ignoreFile)
while ignoreFile in destinationEntrySortedList: destinationEntrySortedList.remove(ignoreFile)
if len(sourceEntrySortedList) != len(destinationEntrySortedList):
print("Manifest lengths differ!")
for (sourceEntryName, destinationEntryName) in zip(sourceEntrySortedList, destinationEntrySortedList):
if sourceEntryName != destinationEntryName:
print("Sorted manifests don't match, %s vs %s" % (sourceEntryName, destinationEntryName))
return False
return True
def compareEntries(self, sourceZip, destinationZip):
sourceInfoList = list(filter(lambda sourceInfo: sourceInfo.filename not in self.IGNORE_FILES, sourceZip.infolist()))
destinationInfoList = list(filter(lambda destinationInfo: destinationInfo.filename not in self.IGNORE_FILES, destinationZip.infolist()))
if len(sourceInfoList) != len(destinationInfoList):
print("APK info lists of different length!")
return False
for sourceEntryInfo in sourceInfoList:
for destinationEntryInfo in list(destinationInfoList):
if sourceEntryInfo.filename == destinationEntryInfo.filename:
sourceEntry = sourceZip.open(sourceEntryInfo, 'r')
destinationEntry = destinationZip.open(destinationEntryInfo, 'r')
if self.compareFiles(sourceEntry, destinationEntry) != True:
print("APK entry %s does not match %s!" % (sourceEntryInfo.filename, destinationEntryInfo.filename))
return False
destinationInfoList.remove(destinationEntryInfo)
break
return True
def compareFiles(self, sourceFile, destinationFile):
sourceChunk = sourceFile.read(1024)
destinationChunk = destinationFile.read(1024)
while sourceChunk != b"" or destinationChunk != b"":
if sourceChunk != destinationChunk:
return False
sourceChunk = sourceFile.read(1024)
destinationChunk = destinationFile.read(1024)
return True
if __name__ == '__main__':
if len(sys.argv) != 3:
print("Usage: apkdiff <pathToFirstApk> <pathToSecondApk>")
sys.exit(1)
ApkDiff().compare(sys.argv[1], sys.argv[2])
Loading…
Cancel
Save