How to make my code secure and prevent from decompiling?

you can use proguard which is by default provided by the Android studio while creating sign apk you can refer below document for that

Link: https://docs.google.com/document/d/1UgEZtKRoAIIXtPLKKHIds33txgU7hH33-3xsoBR4lWY/edit?usp=sharing


You can use DexGuard. Protecting Android applications and SDKs against reverse engineering and hacking.DexGuard offers extensive customization options to enable you to adapt the applied protection to your security and performance requirements.DexGuard prevents attackers from gaining insight into your source code and modify it or extract valuable information from it.

ProGuard is a generic optimizer for Java bytecode. DexGuard is a specialized tool for the protection of Android applications.

Read Dexguard-vs-Proguard


YOUR PROBLEM

I created android app and it is working fine. The issue is that when we decompile the app we can see all the code, so hacker can see our API URL and API Classes so they can clone the app.

Not matter what tool you use to obfuscate or even encrypt code, your API url will need to be in clear text at some point, aka when you do the API request, therefore it's up for grabs by an attacker. So if an attacker is not able to extract it with static binary analyses, it will extract at runtime with an instrumentation framework, like Frida:

Inject your own scripts into black box processes. Hook any function, spy on crypto APIs or trace private application code, no source code needed. Edit, hit save, and instantly see the results. All without compilation steps or program restarts.

So basically the attacker will need to find the place in the code where you do the API request, hook Frida on it, and extract the URL or any secret passed along with it to identify/authorize your mobile app in the API server.

Another approach the attacker can take is to perform a MitM attack in a mobile device he controls, and intercept the request being made to the API server:

MitM attack example Image sourced from article: Steal that API key with a Man in the Middle Attack

As you can see in the above example the intercepted API request, reveals the API server url and the API key being used.

POSSIBLE SOLUTIONS

So my question is that how can I secure my android app so I can protect it from hackers.

When adding security, no matter if for software or for a material thing, is always about layers, see the medieval castles for example, they don't have only one defense, they have several layers of it. So you should apply the same principle to your mobile app.

I will listed some of the minimal things you should do, but not an exhaustive list of them.

JNI/NDK

The JNI/NDK:

The Native Development Kit (NDK) is a set of tools that allows you to use C and C++ code with Android, and provides platform libraries you can use to manage native activities and access physical device components, such as sensors and touch input.

In this demo app I show how native C code is being used to hide the API key from being easily reverse engineered by static binary analyses, but as you already have seen you grab it with a MitM attack at runtime.


#include <jni.h>
#include <string>
#include "api_key.h"

extern "C" JNIEXPORT jstring JNICALL
Java_com_criticalblue_currencyconverterdemo_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {

    // To add the API_KEY to the mobile app when is compiled you need to:
    //   * copy `api_key.h.example` to `api_key.h`
    //   * edit the file and replace this text `place-the-api-key-here` with your desired API_KEY
    std::string JNI_API_KEY = API_KEY_H;

    return env->NewStringUTF(JNI_API_KEY.c_str());
}

Visit the Github repo if you want to see in detail how to implement it in your mobile app.

Obfuscation

You should always obfuscate your code. If you cannot afford a state of the art solution, then at least use the built in ProGuard solution. This increases the time and skills necessary to poke around your code.

Encryption

You can use encryption to hide sensitive code and data, and a quick Google search will yield a lot of resources and techniques.

For user data encryption you can start to understand more about it in the Android docs:

Encryption is the process of encoding all user data on an Android device using symmetric encryption keys. Once a device is encrypted, all user-created data is automatically encrypted before committing it to disk and all reads automatically decrypt data before returning it to the calling process. Encryption ensures that even if an unauthorized party tries to access the data, they won’t be able to read it.

And you can read in the Android docs some examples of doing it:

This document describes the proper way to use Android's cryptographic facilities and includes some examples of its use. If your app requires greater key security, use the Android Keystore system.

But remember, that using Frida will allow for an attacker to hook into the code that returns the data unencrypted, and extract it, but also requires more skill and time to achieve this.

Mobile App Attestation

This concept introduces a new way of dealing with securing your mobile app.

Traditional approaches focus to much on the client side, but in first place the data you want to protect is in the API server, and it's here that you want to have a mechanism that let's you know that what is making the request is really your genuine mobile app, the same you uploaded to the Google Play Store.

Before I dive into the role of the Mobile App Attestation, I would like to first clarify a misconception around what vs who is doing the API request, and I will quote this article I wrote:

The what is the thing making the request to the API server. Is it really a genuine instance of your mobile app, or is it a bot, an automated script or an attacker manually poking around your API server with a tool like Postman?

The who is the user of the mobile app that we can authenticate, authorize and identify in several ways, like using OpenID Connect or OAUTH2 flows.

The Mobile App Attestation role is described in this section of another article I wrote, from where I quote the following text:

The role of a Mobile App Attestation service is to authenticate what is sending the requests, thus only responding to requests coming from genuine mobile app instances and rejecting all other requests from unauthorized sources.

In order to know what is sending the requests to the API server, a Mobile App Attestation service, at run-time, will identify with high confidence that your mobile app is present, has not been tampered/repackaged, is not running in a rooted device, has not been hooked into by an instrumentation framework (Frida, xPosed, Cydia, etc.) and is not the object of a Man in the Middle Attack (MitM). This is achieved by running an SDK in the background that will communicate with a service running in the cloud to attest the integrity of the mobile app and device it is running on.

On a successful attestation of the mobile app integrity, a short time lived JWT token is issued and signed with a secret that only the API server and the Mobile App Attestation service in the cloud know. In the case that attestation fails the JWT token is signed with an incorrect secret. Since the secret used by the Mobile App Attestation service is not known by the mobile app, it is not possible to reverse engineer it at run-time even when the app has been tampered with, is running in a rooted device or communicating over a connection that is the target of a MitM attack.

The mobile app must send the JWT token in the header of every API request. This allows the API server to only serve requests when it can verify that the JWT token was signed with the shared secret and that it has not expired. All other requests will be refused. In other words a valid JWT token tells the API server that what is making the request is the genuine mobile app uploaded to the Google or Apple store, while an invalid or missing JWT token means that what is making the request is not authorized to do so, because it may be a bot, a repackaged app or an attacker making a MitM attack.

So this approach will let your API server to trust with a very high degree of confidence that the request is coming indeed from the same exact mobile app you uploaded to the Google Play store, provided the JWT token has a valid signature and expire time, and discard all other requests as untrustworthy ones.

GOING THE EXTRA MILE

I cannot resist to recommend you the excellent work of the OWASP foundation, because no security solution for mobile is complete without going through The Mobile Security Testing Guide:

The Mobile Security Testing Guide (MSTG) is a comprehensive manual for mobile app security development, testing and reverse engineering.