These days, mobile apps—especially financial ones—are packed with sensitive data and powerful features, making security a top priority for Android developers. And it’s not just financial apps; protecting user data is now essential for every app. That’s why Google Play has introduced new guidelines focused on data security, pushing the entire Android ecosystem to be safer and more reliable.
In this guide, we’ll dive into essential techniques to keep your app secure, including rooting detection, blacklist checks, hardware fingerprinting, Google’s SafetyNet Attestation API, and TEE-backed fingerprint authentication—all with practical examples. Let’s explore how these security measures can give your app the edge it needs to keep users safe.
Introduction to Platform Security
Platform security means making sure your app interacts with the device and any external services in a safe, trusted way. Android gives developers a toolkit of APIs and strategies to spot tampered devices, confirm device identity, and securely authenticate users. By combining these security practices, you can block unauthorized access, detect risky devices, and strengthen your app’s overall security.
Rooted Device Detection
Rooted devices come with elevated privileges, giving deeper access to the operating system. While that sounds powerful, it opens up security risks—malicious actors could access sensitive data, bypass restrictions, and compromise your app’s integrity. That’s why detecting rooted devices is a crucial first step in securing your platform.
object RootDetectionUtils {
private val knownRootAppsPackages = listOf(
"com.noshufou.android.su",
"com.thirdparty.superuser",
"eu.chainfire.supersu",
"com.koushikdutta.superuser",
"com.zachspong.temprootremovejb"
)
private val rootDirectories = listOf(
"/system/app/Superuser.apk",
"/sbin/su",
"/system/bin/su",
"/system/xbin/su",
"/data/local/xbin/su",
"/data/local/bin/su",
"/system/sd/xbin/su",
"/system/bin/failsafe/su"
)
fun isDeviceRooted(): Boolean {
return isAnyRootPackageInstalled() || isAnyRootDirectoryPresent()
}
private fun isAnyRootPackageInstalled(): Boolean {
val packageManager = MyApp.instance.packageManager
return knownRootAppsPackages.any { pkg ->
try {
packageManager.getPackageInfo(pkg, 0)
true
} catch (e: Exception) {
false
}
}
}
private fun isAnyRootDirectoryPresent(): Boolean {
return rootDirectories.any { File(it).exists() }
}
}
Here,
- Root Apps: Common packages associated with rooting are checked.
- Root Directories: Checks if common files associated with rooting exist on the device.
When you call RootDetectionUtils.isDeviceRooted()
, it returns true
if the device is likely rooted.
Device Blacklist Verification
Some devices may have vulnerabilities or unsafe configurations that make them risky for secure applications. This is where device blacklisting comes into play. By comparing a device’s unique identifiers against a list maintained on a secure server, you can block these devices from accessing sensitive parts of your app, helping mitigate security risks.
import android.content.Context
import android.provider.Settings
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONArray
object DeviceBlacklistVerifier {
private const val BLACKLIST_URL = "https://secureserver.com/device_blacklist" // Replace with your actual URL
private val client = OkHttpClient()
suspend fun isDeviceBlacklisted(context: Context): Boolean {
val deviceId = Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
val blacklistedDevices = fetchBlacklist()
return blacklistedDevices.contains(deviceId)
}
private suspend fun fetchBlacklist(): List<String> {
return withContext(Dispatchers.IO) {
try {
// Create a request to fetch the blacklist from your server
val request = Request.Builder().url(BLACKLIST_URL).build()
val response = client.newCall(request).execute()
if (response.isSuccessful) {
val json = response.body?.string() ?: "[]"
val jsonArray = JSONArray(json)
val blacklist = mutableListOf<String>()
for (i in 0 until jsonArray.length()) {
blacklist.add(jsonArray.getString(i))
}
blacklist
} else {
emptyList() // Return an empty list if fetching fails
}
} catch (e: Exception) {
e.printStackTrace()
emptyList() // Return an empty list if there's an error
}
}
}
}
- The
isDeviceBlacklisted
function fetches the device ID and compares it against the list of blacklisted device IDs fetched from a remote server. - The blacklist is fetched asynchronously using
OkHttpClient
to make an HTTP request to your server (you can replaceBLACKLIST_URL
with your actual URL). - The server is expected to return a JSON array of blacklisted device IDs.
Obviously, to create a device blacklist, you first need to gather device IDs when the app is launched. If a user engages in suspicious or malicious activity, you can add their device to the blacklist. From then on, whenever the app is used, the system will check the device ID against the blacklist and block access if there’s a match.
While this method can be effective, it’s important to note that device IDs (like ANDROID_ID
) can sometimes be reset or spoofed. To strengthen security, blacklisting can be combined with other checks such as root detection, device fingerprinting, or behavioral analytics.
Device Blacklisting in Financial Apps
In financial apps, device blacklisting is particularly crucial to protect sensitive information such as banking details, personal accounts, and transaction histories. The primary focus of device blacklisting in financial applications is to prevent access to the app from devices identified as risky. This is done by checking the device ID (such as ANDROID_ID
, IMEI, or device fingerprint) against a predefined blacklist at the moment of access.
If the device ID matches a known compromised or fraudulent device (e.g., a rooted or jailbroken device), the app denies access to critical features such as financial transactions or account management. This helps prevent unauthorized users from accessing sensitive app features and ensures that only trusted devices can interact with the app.
For example, if a device has been flagged as compromised due to rooting, jailbreaking, or involvement in fraud, its device ID is added to the blacklist. On each login attempt, the app checks the device ID against this blacklist and blocks access if a match is found.
Device Blacklisting in Social Media & Dating Apps
While device blacklisting in financial apps focuses on preventing fraud and securing sensitive transactions, social media and dating apps tend to focus more on preventing misuse or abusive behavior. The secondary use of device blacklisting in these apps involves tracking suspicious activity over time, such as repeated rule violations or fraudulent actions, and then blacklisting those devices to prevent further misuse.
In this case, device IDs are often collected for future reference if a device is involved in any misuse or violation of the platform’s terms of service. For example, if a device is used to repeatedly create fake accounts, send spam, or engage in harassment, its device ID could be added to a blacklist. Once blacklisted, that device would be blocked from accessing the app entirely, protecting other users from any malicious activity.
Combining Blacklisting with Other Security Measures
Whether in financial apps or social media platforms, blacklisting should ideally be used in combination with other security mechanisms like root detection, device fingerprinting, and behavioral analytics. This layered approach provides a more comprehensive way to detect and block compromised devices, enhancing overall security.
For example, financial apps may also incorporate two-factor authentication (2FA), while social media apps may use behavioral monitoring to detect suspicious user actions that could trigger a device blacklist.
In short, device blacklisting plays a vital role in protecting apps from risky devices. In financial apps, it primarily focuses on preventing access from compromised devices in real-time, while in social media or dating apps, it may also serve as a tool for blocking devices that engage in malicious behavior or violate platform rules. Combining blacklisting with additional security features ensures a more secure and reliable user experience.
Device Fingerprinting / Hardware Detection
Device fingerprinting is a method used to uniquely identify a device based on its hardware features, making it easier to spot cloned or unauthorized devices trying to fake their identity. The main goal is to ensure that only trusted devices can access services, helping to prevent fraud. This fingerprint can also be used to track devices or authenticate users.
data class DeviceFingerprint(
val androidId: String,
val manufacturer: String,
val model: String,
val serial: String,
val board: String
)
object DeviceFingerprintGenerator {
fun getDeviceFingerprint(): DeviceFingerprint {
return DeviceFingerprint(
androidId = Settings.Secure.getString(
MyApp.instance.contentResolver, Settings.Secure.ANDROID_ID
),
manufacturer = Build.MANUFACTURER,
model = Build.MODEL,
serial = Build.getSerial(),
board = Build.BOARD
)
}
}
// Usage
val fingerprint = DeviceFingerprintGenerator.getDeviceFingerprint()
Here,
- Unique Properties: Collects device-specific information to create a unique fingerprint.
- Serial Check: Uses
Build.getSerial()
if API level permits, adding a layer of uniqueness.
SafetyNet Attestation
Google’s SafetyNet Attestation API assesses the security integrity of an Android device, verifying that it’s not rooted or compromised. To use SafetyNet, you need to integrate Google Play Services. This API requires network access, so ensure your application has the necessary permissions.
In your build.gradle
file, add the SafetyNet dependency
implementation 'com.google.android.gms:play-services-safetynet:18.0.1' // use latest version
Implement SafetyNet Attestation
fun verifySafetyNet() {
SafetyNet.getClient(this).attest(nonce, API_KEY)
.addOnSuccessListener { response ->
val jwsResult = response.jwsResult
if (jwsResult != null) {
// Verify JWS with server for authenticity and integrity.
handleAttestationResult(jwsResult)
}
}
.addOnFailureListener { exception ->
// Handle error
}
}
As we can see,
- SafetyNet Client:
SafetyNet.getClient(context)
initiates the SafetyNet client, enabling attestation requests. - Attestation: The
attest
function generates an attestation result that can be verified on your server. - Nonce: A random value used to ensure the attestation response is unique to this request.
- Verify on Server: To prevent tampering, verify the
jwsResult
on a secure server by validating its JSON Web Signature (JWS). - JWS Result: The JSON Web Signature (JWS) is a token containing attestation results, which should be sent to the server to verify authenticity and device integrity.
TEE-Backed Fingerprint Authentication
TEE-Backed Fingerprint Authentication refers to fingerprint authentication that leverages the Trusted Execution Environment (TEE) of a device to securely store and process sensitive biometric data, such as fingerprints. The TEE is a secure area of the main processor that is isolated from the regular operating system (OS). It provides a higher level of security for operations involving sensitive data, like biometric information.
In Android, TEE-backed authentication typically involves the Secure Hardware or Trusted Execution Environment in combination with biometric authentication methods (like fingerprint, face, or iris recognition) to ensure that biometric data is processed in a secure and isolated environment. This means the sensitive data never leaves the secure part of the device and is not exposed to the operating system, apps, or any potential attackers.
For TEE-backed fingerprint authentication, you should use the BiometricPrompt
approach, as it’s more secure, future-proof, and supports a broader range of biometrics (not just fingerprint) while ensuring compatibility with the latest Android versions.
fun authenticateWithFingerprint(activity: FragmentActivity) {
// Create the BiometricPrompt instance
val biometricPrompt = BiometricPrompt(activity, Executors.newSingleThreadExecutor(),
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
// Authentication successful
// Proceed with the app flow
}
override fun onAuthenticationFailed() {
// Authentication failed
// Inform the user
}
})
// Create the prompt info
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Authenticate")
.setSubtitle("Please authenticate to proceed")
.setNegativeButtonText("Cancel")
.build()
// Start the authentication process
biometricPrompt.authenticate(promptInfo)
}
- BiometricPrompt: Provides a unified authentication dialog for fingerprint, face, or iris, backed by secure hardware (TEE) where available.
- PromptInfo: Configures the authentication dialog, including title, subtitle, and cancellation options.
This approach will automatically use the TEE or secure hardware for fingerprint authentication on supported devices, offering the highest security and compatibility.
Conclusion
By implementing these platform security measures, you can significantly enhance the security and integrity of your Android application. Rooted device detection, device blacklisting, device fingerprinting, SafetyNet attestation, and TEE-backed authentication provide a robust security foundation, making your app resilient against unauthorized access and device-level threats. Always remember that no single security measure is sufficient on its own; combining these approaches maximizes protection for your application and users.