16

Sometimes you need to store a password in the app itself, such as a username/password for communicating with your own server. In these cases it's not possible to follow the normal process of storing passwords - i.e. hash the password, store the hash, compare to hashed user input - because you don't have any user input to compare the hash to. The password needs to be provided by the app itself. So how to protect the stored password in the APK? Would a password-generating function like the one below be reasonably secure?

Plain text:

String password = "$()&HDI?=!";

Simple obfuscation:

private String getPassword(){
    String pool = "%&/@$()7?=!656sd8KJ%&HDI!!!G98y/&%=?=*^%&ft4%(";
    return pool.substring(4, 7) + pool.substring(20, 24) + pool.substring(8, 11);
}

I know ProGuard has some obfuscation capabilities, but I'm curious about what the above "obfuscation" technique does when it's compiled, and how hard it would be for someone to figure it out by looking in the APK and/or using other more sophisticated techniques?

AstroCB
  • 12,337
  • 20
  • 57
  • 73
Magnus
  • 17,157
  • 19
  • 104
  • 189
  • Note that someone attacking this would probably just follow the operations done on the input password guess. Historically there has been a preference to not store actual passwords for comparison, but rather the result of computationally expensive hash functions, with the idea that even if you knew the hash ("the answer") it was expensive to exhaustively find the matching password ("the question"). But in the age of cloud computing and its evil twin the botnet, a moderate length password may be fairly crackable. – Chris Stratton Apr 09 '14 at 00:03
  • See my updated question. I mean the cases where the password has to be provided by the app itself, not by a user. I was a bit unclear on that before, sorry about that. – Magnus Apr 09 '14 at 09:15

1 Answers1

20

tl;dr If you know how to decompile APK, you can easily get the password, no matter how obfuscated the code was. Don't store passwords in APK, it's not secure.

I know ProGuard has some obfuscation capabilities, but I'm curious about what the above "obfuscation" technique does when it's compiled, and how hard it would be for someone to figure it out by looking in the APK and/or using other more sophisticated techniques?

I will show you how easy it is. Here is an Android SSCCE which we will decompile:

MyActivity.java:

public class MyActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        TextView text = (TextView) findViewById(R.id.text);
        text.setText(getPassword());
    }

    private String getPassword() {
        String pool = "%&/@$()7?=!656sd8KJ%&HDI!!!G98y/&%=?=*^%&ft4%(";
        return pool.substring(4, 7) + pool.substring(20, 24) + pool.substring(8, 11);
    }
}

main.xml:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@+id/text"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"/>

After compiling it and running we can see $()&HDI?=! on a TextView.

Let's decompile an APK:

  1. unzip myapp.apk or right-click on the APK and Unzip here. classes.dex file appears.
  2. Convert classes.dex to JAR file with dex2jar. After executing dex2jar.sh classes.dex, classes_dex2jar.jar file appears.
  3. Using some Java decompiler on classes_dex2jar.jar, for example JD-GUI, we retrieve such Java code from MyActivity.class:

    public class MyActivity extends Activity
    {
        private String getPassword()
        {
            return "%&/@$()7?=!656sd8KJ%&HDI!!!G98y/&%=?=*^%&ft4%(".substring(4, 7) 
    + "%&/@$()7?=!656sd8KJ%&HDI!!!G98y/&%=?=*^%&ft4%(".substring(20, 24) 
    + "%&/@$()7?=!656sd8KJ%&HDI!!!G98y/&%=?=*^%&ft4%(".substring(8, 11);
        }
    
        public void onCreate(Bundle paramBundle)
        {
            super.onCreate(paramBundle);
            setContentView(2130903040);
            ((TextView)findViewById(2131034112)).setText(getPassword());
        }
    }
    

ProGuard can't help much, the code will be still easily readable.

Based on the above, I can already give you an answer for this question:

Would a password-generating function like the one below be reasonably secure?

No. As you can see, it increases difficulty of reading deobfuscated code by a tiny bit. We should not obfuscate the code in such way, because:

  1. It gives an illusion of security.
  2. It is a waste of developer's time.
  3. It decreases readability of the code.

In the official Android documentation, in the Security and Design part, they're advising this to protect your Google Play public key:

To keep your public key safe from malicious users and hackers, do not embed it in any code as a literal string. Instead, construct the string at runtime from pieces or use bit manipulation (for example, XOR with some other string) to hide the actual key. The key itself is not secret information, but you do not want to make it easy for a hacker or malicious user to replace the public key with another key.

Ok, so let's try that:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    TextView text = (TextView) findViewById(R.id.text);
    text.setText(xor("A@NCyw&IHY", "ehge13ovux"));
}

private String xor(String a, String b) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < a.length() && i < b.length(); i++) {
        sb.append((char) (a.charAt(i) ^ b.charAt(i)));
    }
    return sb.toString();
}

Gives $()&HDI?=! on a TextView, good.

Decompiled version:

public void onCreate(Bundle paramBundle)
{
    super.onCreate(paramBundle);
    setContentView(2130903040);
    ((TextView)findViewById(2131034112)).setText(xor("A@NCyw&IHY", "ehge13ovux"));
}

private String xor(String paramString1, String paramString2)
{
    StringBuilder localStringBuilder = new StringBuilder();
    for (int i = 0; (i < paramString1.length()) && (i < paramString2.length()); i++) {
        localStringBuilder.append((char)(paramString1.charAt(i) ^ paramString2.charAt(i)));
    }
    return localStringBuilder.toString();
}

Very similar situation like before.

Even if we had extremely complicated function soStrongObfuscationOneGetsBlind(), we can always run decompiled code and see what is it producing. Or debug it step-by-step.

Adam Stelmaszczyk
  • 19,665
  • 4
  • 70
  • 110
  • 15
    I liked your answer, but I lack the advice what to do. "Don't store passwords in APK" does not help much since it's necessary to story the API key somewhere. – arenaq Feb 22 '17 at 11:49
  • 1
    One could at least use JNI for this. Might make it a bit harder to do it. – android developer Oct 20 '20 at 07:19
  • JNI still keeps the secret in plain text in the .so file. See my article where I mention this: https://approov.io/blog/how-to-extract-an-api-key-from-a-mobile-app-with-static-binary-analysis – Exadra37 May 25 '23 at 20:04