How to Send a Semi Secure Request to a Server in Android

Imagine that you have a server for your Android application. What the server does is registering the users of the application and creates accounts for them. But the problem here is, as usual, security. What if a hacker sniff your requests from the application and just creates the same structured requests and sends them to your server to fill its database with fake users. Of course there are some solutions for this type of attack, for example CAPTCHAS, but again the problem is CAPTCHAS will ruin the UX of your application. I mean who wants to write a bunch of nonsense when signing up in Instagram application? So is there a way to somehow stop a hacker from attacking our server without needing us to destroy our valuable UX? Well I believe I have a solution.


The solution has some steps to follow and in each step a problem will arise and we will try to solve them along the way to get to what we want which is a secure valid request to the server.

Step One: Just Respond to Application Requests

If we make the server to only respond to the requests sent from the application we can be a little bit more sure that requests are sent from a real person who is using our application, not a bot or a program of a hacker. But how does the server know a request is sent from the application? Well we can put some kind of encryption into our requests, for example using RSA we can have a private key, put it into the application, and encrypt the requests. So this way the hacker who is sniffing our requests wouldn’t know what we are doing and what information are we sending so they can’t go ahead and make fake requests.

String email = emailEditText.getText().toString();
String password = passwordEditText.getText().toString();

String privateKey = "ourPrivateKey";

String cipherRequest = encrypt(email, password, privateKey);

sendRegistrationRequest(cipherRequest);

But the new problem here is that decompiling an Android application isn’t that hard, actually it’s pretty easy. There are tons of decompiling applications on the web that will decompile our APK to the java code that we have written. Of course there are some obfuscating approaches, like ProGuard that you should use, but they wouldn’t stop a determined hacker to find our private key in our code. So even with obfuscating, after decompilation our code will look like this:

bundle = new EditText(this);
EditText edittext = new EditText(this);
sendRegistrationRequest(encrypt(bundle.getText().toString(), edittext.getText().toString(), "ourPrivateKey"));

As you can see finding the key isn’t that hard. So what do we do?

Step two: Use Native Code

In android you can write some or all of your code in c/c++ languages using NDK. The important thing is you have to know when to use it. I am not going to explain when to use native code but in our situation we are going to benefit from it. Decompiling c/c++ code is possible and there some tools to do it but a c/c++ decompiled code is a complete nonsense. Understanding and debugging a c/c++ decompiled code is super complex and time consuming. And again of course a hacker can go all the way down to the machine code but at least we can make it as hard and as boring and as time consuming as possible. So our step two is to put our private key into the native code. So after doing so our code would look like this:

public native String encrypt(String email, String password);

@Override
protected void onCreate(Bundle savedInstanceState) {
// ...

String email = emailEditText.getText().toString();
String password = passwordEditText.getText().toString();

String cipherRequest = encrypt(email, password);

sendRegistrationRequest(cipherRequest);

// ...
}

But wait a minute! Even if the hacker can’t find our key they can go ahead and manipulate or Java code or take the whole of our c/c++ library and method and call it in their own program with their code. If they create a for loop and generate random emails and passwords they can use our c/c++ library against us and create a ton of fake valid requests. Their code can be like this:

public native String encrypt(String email, String password);

@Override
protected void onCreate(Bundle savedInstanceState) {
// ...

while (true) {
String email = generateRandomEmail();
String password = generateRandomPassword();

String cipherRequest = encrypt(email, password);

sendRegistrationRequest(cipherRequest);
}

// ...
}

So how do we stop this from happening!?

Step three: Use Other Factors

We know that they can use our library just like we are using it. But the advantage of using native code is that we can be, to some extend, sure that:

  • First: They don’t have access to our private key so if they want to create requests they have to use our library.
  • Second: They can’t see or manipulate our native code and logic.

So we can use and check a lot of factors and bring in a lot of checking in our native code. For example to counteract the for loop we can send the encrypted data back to the java code with a little of delay. This will take the power of machine from it and make it more like a normal person who is trying to register many times. And at the same time, delaying for half a second wouldn’t be as annoying as CAPTCHAS for someone who is trying to register. Or we can send something unique along with the email and password to the server so it can find repetitive requests and simply ignore them. That unique thing can be lots of things like the processId that is the identifier of the process’s UID. This is the kernel UID that the process is running under, which is the identity of its app-specific sandbox. The processId wont change when its package is installed on the device so every time that you get your processId it is the same, but it can be different in another device. You can get your processId in native code using getuid(), when including unistd.h and sys/types.h so if the server gets a lot of requests with the same unique factor, here processId, in a short amount of time it can simply ignore them. Also you can check the package name in your native code and a lot of other things. The important key is the hacker doesn’t really know what you are doing in there.

Secured?

No, not really. That is why I put the “semi” word in the title. We did a lot of things and used a lot of methods to make the things hard for the hacker but as I said a determined hacker can do anything. But making things hard for them can dissuade them from doing their job or at least gives you some time to change your approach or find a better way in your next release.