Introduction
In mobile app development, ensuring app security and integrity is crucial. One effective way to do this is by verifying the app's signature at runtime. In this blog, we’ll explore how to implement App Signature Verification in a React Native project using Kotlin for Android’s MainActivity.kt
.
Why is App Signature Verification Important?
App signature verification helps prevent:
- Unauthorized modifications to your app.
- Reverse engineering and tampering.
- Users running an unofficial version of your app.
By checking the app’s signature at launch, you can ensure only the intended release versions are used.
Creating Keystore Files
For apps that will be published on the Google Play Store, a Play Store keystore must be generated and used for signing the final release build.
Before verifying the app signature, a keystore file must be generated. The keystore file is required for signing the app during the release build process. To create a keystore file, run the following command:
keytool -genkey -v -keystore release.keystore -keyalg RSA -keysize 2048 -validity 10000 -alias your_alias_name
This command will generate a release.keystore
file, which will be used to sign the app. Similarly you can do for debug.keystore
or playstore.keystore
also.
Extracting the App Signature
To extract the app signature for verification, navigate to the folder where the keystore file exists and run the following command:
To extract the app signature for verification, navigate to the folder where the keystore file exists and run the following command:
keytool -exportcert -alias your_alias_name -keystore release.keystore | openssl sha1 -binary | openssl base64
Example Output:
gpOIDHFkKCtqPIAvhvsadBlDzIY=
Add this to your .env
file:
APP_SIGNATURE=gpOIDHFkKCtqPIAvhvsadBlDzIY=
For setup, check the react-native-config documentation.
Alternatively, if you don't want to use the APP_SIGNATURE
variable through the .env
, you can use it directly in MainActivity
.
Implementing App Signature Verification in Kotlin
Below is the Kotlin implementation to validate the app's signature at runtime:
MainActivity.kt
package com.my_app
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
import org.devio.rn.splashscreen.SplashScreen
import android.os.Bundle
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.util.Base64
import android.util.Log
import java.security.MessageDigest
import android.app.AlertDialog
class MainActivity : ReactActivity() {
companion object {
private const val VALID = 0 // Indicates a valid app signature
private const val INVALID = 1 // Indicates an invalid or tampered signature
}
private var isAppCompromised = false // Flag to track whether the app is compromised
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
SplashScreen.show(this) // Show splash screen while verifying app signature
val result = checkAppSignature(this) // Validate the app signature
Log.d("LOG", "Signature Validation Result: $result")
if (result != VALID) { // If the signature is invalid
isAppCompromised = true
showAlertDialog(
"Developer Signature is tampered. Please sign in with the correct signature.",
this
) // Show an alert to inform the user
} else {
SplashScreen.hide(this) // Hide splash screen only if the app signature is valid
}
}
override fun getMainComponentName(): String {
return if (isAppCompromised) "" else "my_app" // Prevents the app from running if compromised
}
override fun createReactActivityDelegate(): ReactActivityDelegate {
return DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
}
/**
* Checks if the app's signing certificate matches the expected signature.
*/
private fun checkAppSignature(context: Context): Int {
return try {
// Get the app's package info and signing certificates based on the Android version
val packageInfo: PackageInfo = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SIGNING_CERTIFICATES)
} else {
@Suppress("DEPRECATION")
context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES)
}
// Extract the signatures
val signatures = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
packageInfo.signingInfo.apkContentsSigners
} else {
@Suppress("DEPRECATION")
packageInfo.signatures
}
for (signature in signatures) {
// Generate SHA-1 hash of the signature
val md: MessageDigest = MessageDigest.getInstance("SHA")
md.update(signature.toByteArray())
val currentSignature: String = Base64.encodeToString(md.digest(), Base64.DEFAULT).trim()
Log.d("Current Signature", currentSignature) // Log the extracted signature
Log.d("Expected Signature", BuildConfig.APP_SIGNATURE) // Log the expected signature
// Compare the extracted signature with the expected one from BuildConfig
if (BuildConfig.APP_SIGNATURE.equals(currentSignature, ignoreCase = true)) {
return VALID // Return valid if the signature matches
}
}
INVALID // Return invalid if no matching signature is found
} catch (e: Exception) {
Log.e("SignatureCheck", "Exception in signature check", e) // Log any errors
INVALID
}
}
/**
* Displays an alert dialog when the app signature is invalid.
* Prevents the user from proceeding and forces them to exit.
*/
private fun showAlertDialog(msg: String, context: Context) {
runOnUiThread {
val dialog = AlertDialog.Builder(context)
dialog.setMessage(msg)
.setTitle("Alert")
.setCancelable(false) // Prevent user from dismissing the dialog
.setPositiveButton("Exit") { _, _ -> finish() } // Exit the app when the button is clicked
.create()
.show()
}
}
}
How It Works
1) checkAppSignature(context)
:
- Fetches the current app signature.
- Compares it with the expected signature (
BuildConfig.APP_SIGNATURE
). - Returns
VALID
if they match, otherwiseINVALID
.
2) showAlertDialog(msg, context)
:
- Displays an alert dialog if the app’s signature is tampered with.
- Prevents the user from proceeding further.
Final Thoughts
App signature verification is an essential security measure to ensure that only authorized versions of your app run on users’ devices. Implementing this check prevents unauthorized modifications and enhances the security of your React Native application.
By integrating this approach, your app will remain protected from unauthorized alterations while maintaining user trust and integrity.
Top comments (0)