Home > Enterprise >  Need to obfuscate sensitive data in Android app
Need to obfuscate sensitive data in Android app

Time:10-11

Currently in my android app is secured by DexGuard to obfuscate strings and sensitive information like network API keys which is present inside source code.
I have used DexGuarsd so that no one can reverse engineer it.

However I need to stop DexGuard subscription.
So If I use Android Jetpack Security EncryptedSharedPreferencesand EncryptedFile.Builder then will it help me in achieving security like the one provided by DexGaurd.

For example: If I use EncryptedSharedPreferences like below code, then if someone gets access to my source code OR .apk, then will he/she be able to see the sensitive value PASSWORD-XXXXXXXX or not.
Because the above password is present in my source code file.

encryptedSharedPreferences.edit().apply {
 putString("MY_KEY","PASSWORD-XXXXXXXX"
 }.apply()

My goal is to ensure that even if someone gets my source code then also the above value "PASSWORD-XXXXXXXX" can never be decrypted or seen by anyone.

I need your guidance in achieving it. Please advice the way forward.

CodePudding user response:

Doesn't matter what you do, if one wants to have their hands on strings stored in source you cannot stop them

Exposing network api is fine as it is common practice among various im apps, even if you can make api unreadable from source code somehow, network traffic can simply be sniffed with proper tools rendering all protection features useless

Storing passwords is simply wrong, store a runtime generated hash instead with some salt and pepper here and there (to make it spicy)

Use ssl/tls to make server client conversation hard to read

Remember that a lock only keeps honest people out, put one there but never rely on it

CodePudding user response:

Simple keep your password in app level build.graldle file like:

android {
    compileSdkVersion 30
    buildToolsVersion = '29.0.3'
    defaultConfig {
        buildConfigField 'String', 'prefPass', '"YourPasswordHere"'
        // rest of the code goes here..

And use it like:

BuildConfig.prefPass

As build.gradle file is not part of an APK source code, so no one can steal it.

Why are you saving your password in shared preferences? why can't you use it directly from BuildConfig?. If you really want to save it then encrypt and save it using this class:

import android.util.Log
import java.security.MessageDigest
import java.util.*
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

object CryptographyUtil {

    private const val TAG: String = "CryptographyUtil"
    private const val CIPHER_TRANSFORMATION = "AES/CBC/PKCS5PADDING"
    private const val KEY_ALGORITHM = "AES"

    // https://developer.android.com/guide/topics/security/cryptography#encrypt-message
    fun encryptData(key: String, data: String): String {
        try {
            val secretKeySpec = generateSecretKey(key)
            val cipher = Cipher.getInstance(CIPHER_TRANSFORMATION)
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, getIVSpecification(key))
            val encryptValue = cipher.doFinal(data.toByteArray())
            return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                Base64.getEncoder().encodeToString(encryptValue)
            } else {
                android.util.Base64.encodeToString(encryptValue, android.util.Base64.DEFAULT)
            }
        } catch (e: Exception) {
            Log.d(TAG, "Error while encrypt ${e.message}")
        }
        return data
    }

    fun decryptData(key: String, encryptedData: String): String {
        try {
            val secretKeySpec = generateSecretKey(key)
            val cipher = Cipher.getInstance(CIPHER_TRANSFORMATION)
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, getIVSpecification(key))
            val decodeValue = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                Base64.getDecoder().decode(encryptedData)
            } else {
                android.util.Base64.decode(encryptedData, android.util.Base64.DEFAULT)
            }
            return String(cipher.doFinal(decodeValue))
        } catch (e: Exception) {
            Log.d(TAG, "Error while decrypt ${e.message}")
        }
        return encryptedData
    }

    private fun generateSecretKey(key: String): SecretKeySpec {
        val messageDigest = MessageDigest.getInstance("SHA-256")
        val bytes = key.toByteArray(Charsets.UTF_8)
        messageDigest.update(bytes, 0, bytes.size)
        return SecretKeySpec(messageDigest.digest(), KEY_ALGORITHM)
    }

    private fun getIVSpecification(key: String): IvParameterSpec {
        // concat string so that key has always size greater than 16 bytes & we can
        // get first 16 character for generating IV specification.
        // As documentation suggest that IV specification key can't be less or greater than 16 bytes
        val concatKey = key   key
        return IvParameterSpec(concatKey.substring(0, 16).toByteArray())
    }
}

Use the above class in your EncryptedSharedPreferences class:

val encryptData = encryptData(BuildConfig.prefPass, 
BuildConfig.prefPass)
encryptedSharedPreferences.edit().apply {
    putString("MY_KEY", encryptData)
 }.apply()

Note: To make this work you need to enable minify to hide the BuildConfig file.

buildTypes {
        release {
            debuggable false
            minifyEnabled true
            shrinkResources true
            zipAlignEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        debug {
                minifyEnabled true
                shrinkResources true
                zipAlignEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

Check this answer here

An additional feature of the minifying step is the inlining of constants. This would explain why the BuildConfig disappears, and yet the values still exist where needed. Once the values get inlined, there are no more references to the BuildConfig class and the minifier can remove it entirely

The encryption part is to just prevent Key from being read out of the app, by definition, that's impossible. If the key can be used by your app, it can be used by a modified version of your app that dumps the key to LogCat or something. Tools like ProGuard, DexGuard, and kin make it a bit more difficult to access, but they cannot prevent it. The only way to prevent the key from being accessed is to not have it in the app in the first place. Ref: https://stackoverflow.com/a/30238695/2462531

  • Related