Stay up to date with
news on business
and innovation
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Enhanced Android Security: Build Integrity Verification

Krzysztof Król
Android Developer
Product Development
Security
nomtek_enhanced_android_security

One of the most difficult aspects of creating a good Android application is making sure the app is secure. Certificate pinning and license verification are used commonly in many Android projects to improve security. Unfortunately, these techniques might be easily bypassed by reverse engineering—attackers are even likely to use tools that bypass these security measures automatically. To make attackers' lives much more difficult we encourage you to use a less common security measure: build integrity verification.

Advantages of Using the Build Integrity Verification

A build integrity verification brings many benefits to your project. Consider these advantages:

How Does the Build Integrity Verification Work?

We want to be able to detect changes to application source code and terminate the application once such changes occur. To do this, we can implement a cyclic redundancy check on the application’s bytecode. We will calculate the hash of the classes.dex file and then store it in strings.xml. Then at some point when the application is running, we can invoke the isCodeModified() function.

fun isCodeModified(): Boolean {
       val hash = getString(R.string.crc)
       val entry = ZipFile(app.packageCodePath).getEntry("classes.dex")
       val classesCrc = Long.toHexString(entry.crc)
       return classesCrc != hash
}
     


This function will retrieve the previously cached checksum and then compare it with the current classes.dex checksum value. If those values differ, then we know that the source code was modified and we can terminate the application process. One important point here is that the cached checksum should be stored in application resources, not directly in the source code. Otherwise the classes.dex checksum would change when you modify the cached crc value.

How to Automate the Caching Checksum Value? 

Now that we know how to implement the build integrity verification one question remains: how and when should we store the checksum in strings.xml? Once we are ready for an application release, we could use this simplistic algorithm:

  1. Build apk like we normally would.
  2. Retrieve classes.dex file from apk file and calculate the checksum for that.
  3. Store calculated checksum in strings.xml file and then build application once again.


This process would be cumbersome if we had to perform these steps every time we want to release a new update to Google Play Store. Also, it’s common these days to use a continuous integration system to automate application builds. To address these issues, we will create two gradle tasks:

static def getChecksum() {
   def command = "crc32 PATH_TO_CLASSES_DEX"
   return command.execute().text
}

def getCachedChecksum() {
   //retrieve cached checksum in strings.xml and return it
}


task verifyChecksum() {
   doLast {
       if(getChecksum() != getCachedChecksum()) {
           throw new GradleException()
       }
   }
}

task updateChecksum() {
   doLast {
       //store currentChecksum in strings.xml

   }
}

We left out certain implementation details as an exercise for you.

Now that we have those gradle tasks, we can utilize them in the bash script that will be executed by the CI system:

set -e
./gradlew clean
./gradlew app:assembleRelease 
set +e
if ./gradlew verifyChecksum ; then
   echo "Checksum valid"
else
   set -e
   ./gradlew updateChecksum
   ./gradlew clean
   ./gradlew app:assembleRelease
   ./gradlew verifyChecksum
fi


Here we simply build an application and then verify the checksum. If the checksum is incorrect, then we update it and then build apk once again. Now the checksum should be correct, and we can safely distribute the build to our users.

It’s important to notice that we execute a “clean” task before each build. Otherwise the calculated checksums would differ and our algorithm wouldn’t work correctly. 

The Build Integrity Verification Can Fortify Your Application

Unfortunately, the nature of mobile applications renders them impossible to be resilient to every attack. Potential attackers will always have a possibility to decompile your code and modify it. However, the build integrity verification will make potential attacks much more difficult. Hopefully this article will prove valuable to you, and you will consider implementing this security measure in your future projects. Happy coding!

You may also like