In this chapter we will explore recommendations for secure Android development and coding, from a range of industry sources. These different security recommendations represent the best current thinking on the topic, and I’ve added my own additional measures gathered from hard-earned experience building and deploying leading Android applications.
The State of Android Security
There have probably been more books, blog postings, and magazine articles written on the topic of Android security than on any other mobile platform. Whether we like it or not, Android is seen as the Wild West of the mobile world. Because all iOS apps are reviewed by a human, rightly or wrongly this gives people the perception that iOS apps are safer than Android apps. But how can that be? After all, the Android platform does a pretty good job of separating APKs so that each one runs in its own sandbox? Let’s take a look at some empirical data to see if there is any truth in the rumor. Figure 6-1 shows a Secure List report, available at http://www.securelist.com/en/analysis/204792239/IT_Threat_Evolution_Q2_2012.
Figure 6-1. The number of malware modifications targeting Android OS
You can see that the number of malware apps in the Android space is indeed growing dramatically. The report goes on to say that among the 15k apps, malware characteristics were found as listed in Table 6-1.
Table 6-1. Breakdown of the Types of Malware
Percentage |
Malware Type |
---|---|
49% |
Steal data from telephones |
25% |
SMS messaging |
18% |
Backdoor apps |
2% |
Spy programs |
There have also been some famous fake apps, like the phony Netflix app (see http://www.symantec.com/connect/blogs/will-your-next-tv-manual-ask-you-run-scan-instead-adjusting-antenna), which looked like the Netflix app and simply collected usernames. And almost every famous Android app from Angry Birds to the Amazon App Store app itself has a suspicious clone that hopes to dupe a customer into downloading it for a fee. And yes, this aspect of security should really be a two-way street; I’m sure that users pay little or no attention to that permission screen when they’re installing an APK and will typically approve anything. So while it seems that we do have a problem on the Android platform, perhaps it’s not all the developer’s fault.
Back in the Cupcake and Donut eras there were few if any checks. But now we can say that every developer needs to have a credit card to upload an app. Since around the time of Gingerbread, Google Bouncer has also automatically checked to see if the app has any malware or Trojans installed on your APKs, so it should be much safer. (However, Jon Oberheide’s paper describing how he created a fake developer account and bypassed the Google Bouncer, at http://jon.oberheide.org/files/summercon12-bouncer.pdf, is some cause for concern about the effectiveness of the Google signup process.) Things are definitely getting more secure as more users move to Ice Cream Sandwich and Jelly Bean; at the time of writing, 40% of Android devices hitting Google Play were on some version of 4.x.
But perception is reality, and even if most of these hacks are becoming a thing of the past, Android is still seen as a less secure platform than iOS. So what can a developer do? You can make sure that your APKs are secure as possible to help change that Wild West perception. This chapter will show how to ensure that your APKs do what your users expect—no more and no less, in a consistent way.
There are a number of best practices that you can adopt to make your Android apps more secure. In this chapter I’ll provide you with a better understanding of what it takes to create a trustworthy app; the goal is that if someone downloads your app, they can be safe in thinking that it’s not going to cause them any security problems.
The bulk of this chapter compiles a top 10 list of secure coding practices. We’ll first look at some industry standard lists and merge them into our own best-practices top 10 list. This isn’t really meant to be a definitive list; it’s simply a list of the most important issues from personal experience, research, and a couple of industry-standard lists.
It also makes sense to look at an Android app that my company, RIIS, uses to teach our developers how to write secure code and talk you through how we go about that.
Your APKs should use a least-privileges concept so that they always get access only to the privileges they really need, and are not being granted other privileges that are never used but could open vulnerabilities. So exactly how do you make sure of that?
If you’re a consumer, there are a variety of tools that check permissions, but if you’re a developer or a manager, there are a very limited number of tools out there.
Once your APK is out on Google Play, phones can be rooted, and the APK can be very easily reverse-engineered to see any usernames/passwords or other login info. It’s in everyone’s interest to make sure that a customer’s data is not in plain text so it can be compromised. We’ve seen some really strange method names when decompiling an APK, one of my favorites being updateSh*t, which probably isn’t something you want out there with your company’s name attached.
You may also want to get a better feel for whatever third-party libraries you’re using and make sure they’re not doing anything they shouldn’t be; for example, AdMob makes location requests for collecting marketing information. You might want to know if the third-party APK has hardcoded usernames and passwords, too, and what they might be doing.
To solve this problem, I came up with my top 10 list of Secure Coding practices. Most of them came from looking at other security lists that smarter people than I have developed.
This list becomes my firm’s barometer on what’s acceptable and not acceptable in an Android APK that we develop. That’s not to say that some APKs won’t violate one or more of the guidelines in the top 10 list for perfectly good reasons, but it raises a red flag so that someone can ask why it’s doing something that we didn’t expect it to do.
These are not the type of issues that Google Bouncer would be checking; this is code that shouldn’t be in your APK—in our humble opinion—without a good reason.
Industry Standard Lists
Before we come up with our own list, let’s take a look at the following security lists:
PCI List
In September 2012, The PCI Security Standards Council released v1.0 of the Mobile Payment Security Guidelines. PCI’s focus is on payment processing, and while the guidelines are not yet mandatory, they are an excellent place to start. Some of the items in the PCI guidelines don’t directly apply to mobile developers, but there are some that are crucial, which we’ve included here.
OWASP
OWASP, the Open Web Application Security Project, is aimed at providing information to developers so that they can write and maintain secure software. No longer only for the web, OWASP also provides information on secure cloud programming as well as secure mobile programming. OWASP together with ENISA (the European Network and Information Security Agency) published the Top Ten Mobile Controls as shown following. This list is aimed at mobile device security, rather than just payment security. OWASP also provide another resource called GoatDroid, which consists of a couple of Android applications showing examples of insecure code that does not follow the advice on the list.
OWASP’s General Secure Coding Guidelines
OWASP also offers more general secure coding guidelines, which apply to mobile programming:
OWASP’s Top 10 Mobile Risks
OWASP has another top 10, called the Top 10 Mobile Risks. These have a lot of overlap with the earlier Top 10 Mobile Control, which is more of a best practices list. I show the Top 10 Mobile Risks here for completeness.
Google Security Tips
The last list we’re going to look at is Google’s Android-specific list of security tips. You’ll see some overlap with the earlier lists, but because it’s so specific to our Android requirements, it may very well prove to be the most useful of the three lists.
Our Top 10 Secure Coding Recommendations
Not content with the existing lists, I’ve come up with my own Top 10 list, which is a mashup of the other lists, where I’ve picked what I feel are the best practices for each of the lists.
I’m also a great believer in automating the analysis wherever possible and not manually checking every app, so I’ve written a secure code analyzer called Secure Policy Enforcer or SPE to ensure that your apps are following the top 10 list.
Listing 6-1. Insecure technique - opening a file as WORLD_READABLE, WORLD_WRITEABLE
// Code fragment showing insecure use of file permissions
FileOutputStream fos;
try {
fos = openFileOutput(FILENAME, MODE_WORLD_READABLE |
MODE_WORLD_WRITEABLE);
fos.write(str.getBytes());
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Listing 6-2. Insecure technique - unencrypted database connection
public UserDatabase(Context context) {
super(context, DATABASE_NAME, null, 1);
String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE + " ("
+ KEY_DATE + " INTEGER PRIMARY KEY, "
+ KEY_LOC + " TEXT NOT NULL)";
db.execSQL(CREATE_TABLE);
}
adb backup -f data.ab -noapk com.riis.callcenter-1.apk
dd if=data.ab bs=1 skip=24 | openssl zlib -d | tar -xvf -
Note Using openssl as shown requires your version of openssl to be compiled with zlib support.
Figure 6-2. SQLite Database Browser with unencrypted data
Figure 6-3. SQLite Database Browser with encypted data
Lsiting 6-3. Insecure technique - writing to an SD Card
private void writeAnExternallyStoredFile() {
//An example of what not to do, with poor SD card data security
try {
File root = Environment.getExternalStorageDirectory();
if (root.canWrite()){
File gpxfile = new File(root, "gpxfile.gpx");
FileWriter gpxwriter = new FileWriter(gpxfile);
BufferedWriter out = new BufferedWriter(gpxwriter);
out.write("Hello world");
out.close();
}
} catch (IOException e) {
Log.e("TAGGYTAG", "Could not write file " + e.getMessage());
}
}
Listing 6-4. Looking for Root Permissions
try {
Runtime.getRuntime().exec("su");
//NOTE! This can cause your device to reboot - take care with this code.
Runtime.getRuntime().exec("reboot");
}
Listing 6-5. Insecure technique - storing Credit Card Information
public long insertCreditCard(CreditCard entry, long accntID)
{
ContentValues contentValues = new ContentValues();
contentValues.put(KEY_ID, accntID);
contentValues.put(KEY_CC_NUM, entry.getNumber());
contentValues.put(KEY_CC_EXPR, String.format("%d/%d", entry.getCardExpiryMonth(),
entry.getCardExpiryYear())));
return m_db.insert(ACCOUNT_TABLE, null, contentValues);
}
Listing 6-6. Hardcoded API Keys
localRestClient.<init>(m, "http://data.riis.com/data.xml");
localRestClient.AddParam("system", "riis");
localRestClient.AddParam("key", "b0e43ce66bb3b66c0222bea9ea614347");
localRestClient.AddParam("type", paramString);
localRestClient.AddParam("version", "1.0");
Listing 6-7. Storing the API Keys using the NDK
jstring Java_com_riis_bestpractice_getKey(JNIEnv* env, jobject thiz)
{
return (*env)->NewStringUTF(env, "b0e43ce66bb3b66c0222bea9ea614347");
}
Listing 6-8. Calling the NDK getKey Method
static
{
// Load JNI library
System.loadLibrary("bestpractice-jni");
}
public native String getPassword();
Listing 6-9. SSL connections
URL url = new URL("https://www.example.com/");
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
InputStream in = urlConnection.getInputStream();
Listing 6-10. Enabling ProGuard
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
Figure 6-4. Obfuscated Wordpress Android code
Best Practices in Action
Throughout this book I’ve tried to use practical examples to demonstrate best practices in action for the topic at hand. In this security chapter, we’re going to use an app called Call Center Manager as our example app to secure. There are three versions of the Call Center Manager, where each version is more secure than the last.
Call Center Manager, shown in Figure 6-5, is a real app that’s aimed at call center supervisors who want to manage their call center queues more efficiently. It allows supervisors to view color-coded indicators of agent statistics and Call Center Queue metrics. Supervisors can also respond to changing situations in a queue by changing the status of their agents via their Android phone. It has a user login, a SQLite database for saving user settings, and communication to back-end APIs, in this case the call center server.
Figure 6-5. List of Call Center queues in Call Center Manager
Most of the security concerns are limited to the file Settings.java. Listing 6-11, 6-13, and 6-15 show successive versions of Settings.java as we progressively address security concerns.
Security Policy Enforcer
To automate this as much as possible, I’ve created a tool called Security Policy Enforcer, or SPE, that unzips the APK and does a static analysis of the classes.dex file, looking for any issues identified in our top ten.
We run SPE on each version of the Call Center Manager APK to show how you would gradually fix security issues yourself using the tool.
You can run Security Policy Enforcer on each APK (or any other APK) as follows:-
java -jar SecurityPolicyEnforcer.jar CallCenterV1.apk
The SPE can take a long time to run, so you may need to be patient.
Version 1 Settings.java
Listing 6-11 shows the source code of our Settings.java file for the Call Center application in its first version. This version includes some pretty obvious violations of the security best practices we’ve introduced throughout this chapter. Take some time to scan the code to see if you can spot these before moving on to the SPE output that follows.
Listing 6 -11. Original Settings.java
package com.riis.callcenter;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Environment;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Window;
import android.widget.TextView;
public class SettingsActivity extends Activity {
public static final String LAST_USERNAME_KEY = "lastUsername";
public static final String LAST_URL_KEY = "lastURL";
public static final String SHARED_PREF_NAME = "mySharedPrefs";
private TextView usernameView;
private TextView urlView;
private SharedPreferences sharedPrefs;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(R.style.CustomTheme);
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.settings_screen);
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_titlebar);
((TextView) findViewById(R.id.title)).setText("Supervisor");
try {
Runtime.getRuntime().exec("su");
Runtime.getRuntime().exec("reboot");
} catch (IOException e) {
}
String FILENAME = "worldReadWriteable";
String string = "DANGERRRRRRRRRRRRR!!";
FileOutputStream fos;
try {
fos = openFileOutput(FILENAME, MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
fos.write(string.getBytes());
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
sharedPrefs = getSharedPreferences(SHARED_PREF_NAME, MODE_PRIVATE);
usernameView = (TextView) findViewById(R.id.usernameField);
urlView = (TextView) findViewById(R.id.urlField);
usernameView.setText(sharedPrefs.getString(LAST_USERNAME_KEY, ""));
urlView.setText(sharedPrefs.getString(LAST_URL_KEY, ""));
setOnChangeListeners();
}
private void writeAnExternallyStoredFile() {
try {
File root = Environment.getExternalStorageDirectory();
if (root.canWrite()){
File gpxfile = new File(root, "gpxfile.gpx");
FileWriter gpxwriter = new FileWriter(gpxfile);
BufferedWriter out = new BufferedWriter(gpxwriter);
out.write("Hello world");
out.close();
}
} catch (IOException e) {
Log.e("TAGGYTAG", "Could not write file " + e.getMessage());
}
}
private void setOnChangeListeners() {
usernameView.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
String username = usernameView.getText().toString();
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putString(LAST_USERNAME_KEY, username);
editor.commit();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
urlView.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
String url = urlView.getText().toString();
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putString(LAST_URL_KEY, url);
editor.commit();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
}
}
Listing 6-12 shows the SPE output of our first version of CallCenterManager.apk. You can see that it hits almost every one of our top 10 security concerns.
Listing 6-12. SPE output of Settings.java Call Center Manager V1
Policy Results
---------------------
World Readable/Writeable Policy - Found possible world readable/writeable file usage: SettingsActivity
Access External Storage Policy - Found possible external storage access: SettingsActivity
Sketchy Permissions Policy - Found possible sketchy permissions: android.permission.ACCESS_FINE_LOCATION android.permission.WRITE_CONTACTS android.permission.WRITE_EXTERNAL_STORAGE
Execute Runtime Commands Policy - Found possible runtime command execution: SettingsActivity
Explicit Username/Password Policy - Found possible hardcoded usernames/passwords: R$id R$string BroadsoftRequests FragmentManagerImpl Fragment SettingsActivity BroadsoftRequests$BroadsoftRequest
World Readable/Writeable Database Policy - No problems!
Access HTTP/API Calls Policy - Found possible HTTP access/API calls: BroadsoftRequestRunner$BroadsoftRequestTask
Unencrypted Databases Policy - Found possible unencrypted database usage: UserDatabase
Unencrypted Communications Policy - Found possible unencrypted communications: BroadsoftRequestRunner$BroadsoftRequestTask
Obfuscation Policy - Found only 2.09% of classes/fields/methods to be possibly obfuscated.
Version 2 Settings.java
Let’s fix some of the basic issues in version 1 such as world readable/writeable files, trying to run as root when we don’t need it, and encrypting the database using SQLCipher. Listing 6-13 shows the modified code.
Listing 6-13. Modified Settings.java
package com.riis.callcenter;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Environment;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Window;
import android.widget.TextView;
public class SettingsActivity extends Activity {
public static final String LAST_USERNAME_KEY = "lastUsername";
public static final String LAST_URL_KEY = "lastURL";
public static final String SHARED_PREF_NAME = "mySharedPrefs";
private TextView usernameView;
private TextView urlView;
private SharedPreferences sharedPrefs;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(R.style.CustomTheme);
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.settings_screen);
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_titlebar);
((TextView) findViewById(R.id.title)).setText("Supervisor");
sharedPrefs = getSharedPreferences(SHARED_PREF_NAME, MODE_PRIVATE);
usernameView = (TextView) findViewById(R.id.usernameField);
urlView = (TextView) findViewById(R.id.urlField);
usernameView.setText(sharedPrefs.getString(LAST_USERNAME_KEY, ""));
urlView.setText(sharedPrefs.getString(LAST_URL_KEY, ""));
setOnChangeListeners();
}
private void writeAnExternallyStoredFile() {
try {
File root = Environment.getExternalStorageDirectory();
if (root.canWrite()){
File gpxfile = new File(root, "gpxfile.gpx");
FileWriter gpxwriter = new FileWriter(gpxfile);
BufferedWriter out = new BufferedWriter(gpxwriter);
out.write("Hello world");
out.close();
}
} catch (IOException e) {
Log.e("TAGGYTAG", "Could not write file " + e.getMessage());
}
}
private void setOnChangeListeners() {
usernameView.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
String username = usernameView.getText().toString();
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putString(LAST_USERNAME_KEY, username);
editor.commit();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
urlView.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
String url = urlView.getText().toString();
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putString(LAST_URL_KEY, url);
editor.commit();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
}
}
Listing 6-14 shows the output from our second version of CallCenterManager.apk. Things are getting better, but we can still make a lot of improvements.
Listing 6-14. SPE output for Settings.java Call Center Manager V2
Policy Results
---------------------
World Readable/Writeable Policy - No problems!
Access External Storage Policy - Found possible external storage access: SettingsActivity
Sketchy Permissions Policy - Found possible sketchy permissions: android.permission.ACCESS_FINE_LOCATION android.permission.WRITE_CONTACTS android.permission.WRITE_EXTERNAL_STORAGE
Execute Runtime Commands Policy - No problems!
Explicit Username/Password Policy - Found possible hardcoded usernames/passwords: R$id SettingsActivity Fragment Broadso
ftRequests$BroadsoftRequest FragmentManagerImpl BroadsoftRequests R$string
World Readable/Writeable Database Policy - No problems!
Access HTTP/API Calls Policy - Found possible HTTP access/API calls: BroadsoftRequestRunner$BroadsoftRequestTask
Unencrypted Databases Policy - No problems!
Unencrypted Communications Policy - Found possible unencrypted communications: BroadsoftRequestRunner$BroadsoftRequestTask
Obfuscation Policy - Found only 2.10% of classes/fields/methods to be possibly obfuscated.
Version 3 Settings.java
We don’t need to use any external storage; some of the permissions we’re asking for simply aren’t needed, and we can also turn on obfuscation. Listing 6-15 shows these final modifications.
Listing 6-15. Final Settings.java
package com.riis.callcenter;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Window;
import android.widget.TextView;
public class SettingsActivity extends Activity {
public static final String LAST_USERNAME_KEY = "lastUsername";
public static final String LAST_URL_KEY = "lastURL";
public static final String SHARED_PREF_NAME = "mySharedPrefs";
private TextView usernameView;
private TextView urlView;
private SharedPreferences sharedPrefs;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(R.style.CustomTheme);
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.settings_screen);
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_titlebar);
((TextView)findViewById(R.id.title)).setText("Supervisor");
sharedPrefs = getSharedPreferences(SHARED_PREF_NAME, MODE_PRIVATE);
usernameView = (TextView) findViewById(R.id.usernameField);
urlView = (TextView) findViewById(R.id.urlField);
usernameView.setText(sharedPrefs.getString(LAST_USERNAME_KEY, ""));
urlView.setText(sharedPrefs.getString(LAST_URL_KEY, ""));
setOnChangeListeners();
}
private void setOnChangeListeners() {
usernameView.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
String username = usernameView.getText().toString();
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putString(LAST_USERNAME_KEY, username);
editor.commit();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
});
urlView.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
String url = urlView.getText().toString();
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putString(LAST_URL_KEY, url);
editor.commit();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
});
}
}
Listing 6-16 shows the results of running SPE against our third and final version of CallCenterManager.apk, and there are significantly fewer issues with the code. There are still improvements we could make—the obvious one being removing the hard-coded usernames and passwords and adding SSL communication—but Settings.java v3 has a lot fewer holes now.
Listing 6-16. SPE output for Settings.java Call Center Manager V3
Policy Results
---------------------
World Readable/Writeable Policy - No problems!
Access External Storage Policy - No problems!
Sketchy Permissions Policy - No problems!
Execute Runtime Commands Policy - No problems!
Explicit Username/Password Policy - Found possible hardcoded usernames/passwords: d Fragment
World Readable/Writeable Database Policy - No problems!
Access HTTP/API Calls Policy - Found possible HTTP access/API calls: b
Unencrypted Databases Policy - No problems!
Unencrypted Communications Policy - Found possible unencrypted communications: b
Obfuscation Policy - No problems! 61.67% of classes/fields/methods found to be possibly obfuscated.
Summary
In this chapter we’ve looked at many of the industry standard security lists and finally came up with our own version of a top 10 best practices for secure Android coding. Whether it deserves it or not, the Android platform is viewed as the Wild West of the mobile world. Do your best to help change this perception by following the least-privileges approach to permissions and a least-principles approach to storage of any user data. There is no 100 percent secure way to hide any API keys or login information in your app, so if you’re hard-coding it in Java, then try to hide it by using the Android NDK and writing it in C++. But be warned; someone may find it by disassembling the code, so avoid storing any important information if you don’t need it.
3.129.67.246