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 EncryptedSharedPreferences
and 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