1

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.

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
SVK
  • 676
  • 1
  • 5
  • 17
  • 4
    *“ 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.“* that is impossible no matter the technology you use, not with dexguard or anything else. If your code at some point needs the clear text value there is a way to retrieve it and someone can steal it. – luk2302 Oct 07 '21 at 05:42
  • Move password specific functionality out of your app, perhaps to a Server – rupinderjeet Oct 07 '21 at 06:13

2 Answers2

2

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

asim
  • 533
  • 6
  • 17
-4

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

Shailendra Madda
  • 20,649
  • 15
  • 100
  • 138
  • This will be very easy to retrieve. – rupinderjeet Oct 07 '21 at 06:10
  • 2
    *"As build.gradle file is not part of an APK source code, so no one can steal it."* - Where do you think Android will store `BuildConfig.java`? – rupinderjeet Oct 07 '21 at 06:16
  • @rupinderjeet Yes `BuildConfig` is not packaged into APK. – Shailendra Madda Oct 07 '21 at 06:40
  • 2
    @ShailendraMadda Without inlining `val x = BuildConfig.someField`, with inlining `val x = someFieldValue`. inlining doesn't hide anything, it just copies the contents where they are required. this means if you access `BuildConfig.presPass` from 10 files, it will be copied to each one of them – mightyWOZ Oct 07 '21 at 06:50
  • 1
    Hi @ShailendraMadda : Thank you very much for the detailed explanation. This information is really helpful. I also read the reference links. I request you to please share where the secret value will reside once we use `build.gradle` ?. Please elaborate for my understanding. and secondly will it be safe from reverse engineering, because it is a production app – SVK Oct 07 '21 at 12:53
  • Yeah, I did this and when I searched for my key after reverse engineering, I couldn't find it anywhere. You can try this out by decompiling your APK using http://java-decompiler.github.io/ – Shailendra Madda Oct 07 '21 at 13:23
  • Hi @ShailendraMadda: I still have two queries: (1) Why in above solution we are encrypting data even after storing sensitive data in `build.gradle` (2) In one of the reference link you gave above, I got an update there that. this method of using `build.gradle ` is not safe. Please see the comments here from `David Lu` . Link: https://stackoverflow.com/questions/56318540/where-can-i-find-my-buildconfig-in-the-apk-analyzer-after-minifying-the-code-wit – SVK Oct 08 '21 at 06:40
  • 1) We are encrypting because if the hacker accesses your shared preferences then he can see the password but can't understand as it is encrypted. 2) Yeah it's not safe that's why we are hiding the `BuildConfig` file by enabling the minify. So you are saving encrypted code in shared pref and hiding the original password which is in BuildConfig. So you are safe to go with it. – Shailendra Madda Oct 08 '21 at 07:54
  • @ShailendraMadda : I really thank you for all your support. Please note that I followed your solution but it does not work. I added `debuggable false, minifyEnabled true, shrinkResources true, zipAlignEnabled true` to my gradle, but when I analyzed the apk I can see the Build.Config in `classes3.dex`. And I could also see my secret value there. I have uploaded the image here : https://ibb.co/Yd70mny – SVK Oct 10 '21 at 08:21
  • Are you analyzing signed APK? – Shailendra Madda Oct 14 '21 at 11:48
  • Try to generate a signed APK and try to find the key by reverse engineering using JAD GUI https://github.com/java-decompiler/jd-gui/releases and let me know if you can find it. – Shailendra Madda Oct 14 '21 at 11:53