adjoe Engineers’ Blog
 /  Android  /  Obfuscation in Android Apps
Purple illustration as decorative image
Android

Improving Code Obfuscation in Android Apps

We all have our secrets. This is true for individuals as well as companies. If a secret becomes known to the public, this usually leaves the party who tried to withhold the secret in a bad spot. 

For a company, the secret could be the key to its business. If it gets leaked, it could give their competitors an advantage and potentially ruin the company. This secret could be for example a new algorithm that a company is developing, a more advanced way of collecting information, or simply paths to internal APIs.

Mobile developers are forced to include the code for these secrets in their applications. If the code isn’t there, and if the code doesn’t contain the logic, it cannot do anything. At the same time, this means that the secrets can end up in the user’s hands, who can then do with them whatever they want. For example, they could call the internal APIs with fake parameters, which can potentially harm the business.

The best way to prevent this is, of course, not to add critical code to your mobile apps. But if that isn’t possible, then the only way to protect the code is through obfuscation. Effective code obfuscation is important for protecting your business secrets and, with that, your business.

In this article, I will explain the principles of code obfuscation and provide examples of implementing different types of obfuscation in Android apps.

Obsuf … Pobf … Obf … What???

What is obfuscation? Many Android developers are already familiar with the concept. If you are one of them, feel free to skip this paragraph. But if you are unsure about what obfuscation is or why you might need it, keep reading.

In essence, code obfuscation is a technique that scrambles your code. It doesn’t change the order in which the code is executed because that would most of the time change its functionality. But it changes the order in which the statements appear in the source code. 

Obfuscation can also change identifier names, conceal computations, and even add bogus code that doesn’t have any effect. All of this makes the code more difficult to understand for third parties and thus reduces the chance of someone else using your code.

So, How Does Obfuscation Work?

There are many ways to obfuscate code. However, an easy way to approach it is the following.

Think about all the principles of writing good, high-quality code: It should be readable; understandable; maintainable; and apply DRY, SOLID, KISS – and all those other principles. To obfuscate code, take the principles and reverse them!

val K = -3
fun v(v: List<Int>): Double {
   var (a, b) = 0 to 0.0;
   var c = 0
   if ((System.currentTimeMillis() shr 63) == 0L) a = v.size
   else b = v[0].toDouble()
   while (a > 0) {
       b += v[c + (a xor a)] * -((a xor -a) shr K)
       c += a / a--
   }
   return b / c
}
val K = 31

Ugly code, beautifully obfuscated: can you guess what it does? See below for the solution.

Why? Again, code obfuscation is essentially writing code that is unreadable and hard to understand. Therefore, by writing the ugliest code you effectively obfuscate the code.

There is one important thing to keep in mind, though. If you obfuscate source code, you obfuscate it not only for third parties that are not supposed to read it, but also for yourself and your coworkers. These are people who should still understand the code because they have to work with it!

To solve this problem, code obfuscation is usually applied not on the source code level, but on the bytecode level at compile time. This means that whatever effect the obfuscation has won’t be visible in the source code.

Bytecode is not easy to read, and it’s even harder to write. That’s why obfuscation on the bytecode level is typically not done manually but with the help of programs that apply the obfuscation automatically.

fun average(numbers: List<Int>): Double {
   return numbers.sum().toDouble() / numbers.size
}

The unobfuscated code from the example above. Now it’s clear that it calculates the average of a list of input numbers. To obfuscate it, I added additional variables, dead code and of course a good portion of bit shift magic.

There are many programs for obfuscation – both proprietary and paid as well as open source and free. Among the free and open-source ones, ProGuard is probably the most famous one for Android developers, since it is the default obfuscator and code shrinker for all Android apps.

ProGuard, however, doesn’t provide the most powerful obfuscation. To illustrate this, look at the following example and compare the unobfuscated code with the code that was obfuscated by ProGuard.

code snippet showing before and after code obfuscation in android apps

You will notice that all that ProGuard did is change the names of the method, its parameters, and local variables. While this is already quite useful – after all, names should always be chosen in a way that they describe what the variables or method is supposed to do – the rest of the method remains unobfuscated, and it is quite easy to determine what it does.

The reason for that is that ProGuard was not created with obfuscation in mind but rather with code minification and optimization. And it is really good at that! But if you want to have something that is really good at obfuscation you have to look further.

Integrating Third-Party Obfuscators

An easy way to add string and control-flow obfuscation to your Android app is through OpenObfuscator by dProtect. At the time of writing, this is the most popular free and open-source obfuscator specifically for Android. It has configuration capabilities similar to ProGuard and is fully compatible with ProGuard. The only downside is that it doesn’t seem to be actively supported anymore.

To add OpenObfuscator to your project, you simply need to add it as a Gradle plugin:

buildscript {
   dependencies {
       classpath 're.obfuscator:dprotect-gradle:1.0.0'
   }
   repositories {
       mavenCentral()
       maven {
           url = uri("https://maven.pkg.github.com/open-obfuscator/dProtect")
           credentials {
               username = "xxx"
               password = "xxx"
           }
       }
   }
}

Read this article to learn how you can generate the GitHub personal access token that you need to download the plugin.

With that done, all that’s left to do is create a configuration file. Next to string and control-flow obfuscation, OpenObfuscator also supports arithmetic and constant obfuscation (which can be seen as a special case of control-flow obfuscation). The following configuration file enables all of them for the entire project.

-obfuscate-arithmetic,high class io.adjoe.obfuscation.** { *; }
-obfuscate-constants class io.adjoe.obfuscation.** { *; }
-obfuscate-control-flow class io.adjoe.obfuscation.** { *; }
-obfuscate-strings class io.adjoe.obfuscation.** { *; }

Now it’s time to build your application. 

While that is running, let’s talk about performance for a second. As obfuscation almost always introduces additional code, it does impact the performance of your application negatively. However, you will find that the impact is usually not big and not noticeable by the user, unless you overdo it. It certainly will not cause ANRs

In the end, it is a tradeoff between security and performance. If performance is the most important thing for you, you can always scale the level of obfuscation down and only obfuscate those parts of the code that really require it (after all, is it really such a big secret how you change the button’s color to red?).

After the compilation has finished, we can inspect the results. Here you can see an example of control-flow, arithmetic, and constant obfuscation. Do you recognize the method?

fun a(n: Int): Int {
    var l: Long
    val lArray: LongArray = b
    var n2: Int
    while ((0x377A2C62.toInt() xor (lArray[0].toInt().also { n2 = it })) % (lArray[1].toInt() xor 0x52BE2870) != 0) {}
    var n3 = (lArray[2].toInt() xor 0x5B45B7AF)
    var n4 = (lArray[3].toInt() xor 0x780DEDF0)
    var n5 = (lArray[1].toInt() xor 0x52BE2870)
    n2 = n4
    if (n5 <= n) {
        n2 = n3
        while (true) {
            n3 += n4
            l = b[3]
            val n6 = l.toInt()
            n2 = n3 - (n6 xor 0x780DEDF0) - n3 + (n3 + (l.toInt() xor 0x780DEDF0) + n2)
            while (n5 != n) {
                lArray = b
                l = lArray[3]
                val n7 = l.toInt()
                n5 += (l.toInt() xor 0x780DEDF0) * 2 + (~(n5) or (n7 xor 0x780DEDF0)) + (n7 + n5 - ((l.toInt() xor 0x780DEDF0) + n5 + (l.toInt() xor 0x780DEDF0) + (~(l.toInt() xor 0x780DEDF0) or n6)))
                a = (lArray[9].toInt() xor 0x673E7E26).also { n3 = it }
                if ((n3 * n3 + n3 + (lArray[4].toInt() xor 0x1EDF9B91)) % (lArray[7].toInt() xor 0x14F34D4B) == 0) {
                    n4 = n2
                    continue
                }
                n3 = n4.also { n4 = n2 }
                n2 = n3
            }
            break
        }
    }
    return n2
}

Yes, this is the fibonacci method from above. Who would have guessed? Comparing this to the obfuscation that ProGuard applied to the same method, you can see what good obfuscation can do and how it can protect your application.

Create Your Own Obfuscator

Okay, we know how we can greatly improve the obfuscation of our code by using custom third-party obfuscators. But if others can do it, why shouldn’t we build our own obfuscator for Android? Is that possible? 

The answer is yes, it is possible. And it is even a really good idea. Not only because it’s a fun project, but also because you can implement custom obfuscation algorithms that are not yet known to deobfuscators. This makes your obfuscation even more secure.

But even though the concept of writing an obfuscator for Android is not that complicated, it would still be too much for this article. I will save this for a later article, so stay tuned!

Putting It All Together

You have learned what obfuscation is and why it’s beneficial to use code obfuscation in Android apps. We went over different obfuscation methods, from simple manual obfuscation over automatic obfuscation with ProGuard to more advanced techniques like control-flow obfuscation. You saw how easy it is to integrate an open-source obfuscator that adds more powerful obfuscation than ProGuard which improves the security of your application.

I recommend using obfuscation in all parts of your application that don’t contain publicly known information (for example, a simple Fibonacci number computation). You can hide your internal API paths using string obfuscation and other secret computations using, for example, control-flow obfuscation.

Keep in mind: This doesn’t make your app secure, but it makes it more secure and in the best case makes attackers lose their minds when reverse-engineering your app.

If you would like to see obfuscation in action or simply check out the code examples from this article again in detail, take a look at this GitHub repository. Here I have put everything together in a runnable application. It also implements OpenObfuscator, which will make it easier for you to get started with it in your own applications.

Product Lead (f/m/d)

  • adjoe
  • Programmatic Supply
  • Full-time
adjoe is a leading mobile ad platform developing cutting-edge advertising and monetization solutions that take its app partners’ business to the next level. Part of the applike group ecosystem, adjoe is home to an advanced tech stack, powerful financial backing from Bertelsmann, and a highly motivated workforce to be reckoned with.

Meet Your Team: WAVE Supply Services
In this competitive adtech market, adjoe stands for greater transparency and fairness for app publishers and advertisers – and a more relevant and enjoyable experience for users.
It’s exactly for this that adjoe has built its own programmatic mobile ad platform WAVE which connects app publishers with advertisers. We are working with dozens of advertising networks, measurement providers and other external services with whom we exchange millions of data points every minute. The WAVE Supply Services team is responsible for developing tools through which app publishers can manage their WAVE integration, analyze their ad monetisation performance and assess the ads’ UX through dashboards and APIs.

Join our discussions, explore implementation, and put your problem-solving skills to the test in our cross-functional Programmatic team!

As a part of the WAVE Supply Services team, you’ll be responsible for developing the face of the product: services to set up an SDK, analyze ad revenue and apps’ UX – from gathering the necessary data from our SDK to visualizing it on the dashboard or providing it via APIs.
What You Will Do
  • Build and manage a cross-functional team together with a Tech Lead: do hiring, feedback exchange, organize routines in the team.
  • Conduct competitors research, market research, customer research to come up with business requirements for the product.
  • Work with designers to prepare mockups for the UI of the product.
  • Prepare and communicate requirements to the engineers.
  • Take care of critical product documentation about features and business logic (both internal and external).
  • Work with statistics from different sources (internal system dashboards, BI tools, spreadsheets) on a regular basis to understand product use cases and identify issues.
  • Align with the company’s overall strategic goals. Work on long-term product roadmap and quarterly OKRs.
  • Align with other tech teams on common guidelines for development, design, etc.
  • Who You Are
  • You have a degree in information technology, economics, analytics, or a similar field or 5 years working experience in product or analyst positions.
  • You have 2+ years’ of experience working as Product Lead / Manager / Owner – preferably in B2B SaaS products, developing web applications.
  • You have experience hiring people, doing regular 1-1s, creating career plans.
  • You can speak both tech & business languages: discuss feature implementation with engineers and business needs with business development colleagues.
  • You have experience in basic data analysis and extracting valuable insights from data.
  • You have experience in working with task-tracking tools (Jira, GitLab, etc).
  • Plus: You have experience in using BI tools (QuickSight, Tableau, MS PowerBI).
  • Plus: You know how to work with SQL, Python, or R.
  • Plus: You experience managing mobile app/SDK development.
  • Heard of Our Perks?
  • Tech Package: Create game-changing technologies and work with the newest technologies out there.
  • Wealth building: virtual stock options for all our regular employees.
  • Work-Life Package: 2 remote days per week, 30 vacation days, 3 weeks per year of remote work, flexible working hours, dog-friendly kick-ass office in the center of the city.
  • Relocation Package: Visa & legal support, relocation bonus, reimbursement of German Classes costs and more.
  • Happy Belly Package: Monthly company lunch, tons of free snacks and drinks, free breakfast & fresh delicious pastries every Monday
  • Physical & Mental Health Package: In-house gym with personal trainer, various classes like Yoga with expert teachers.
  • Activity Package: Regular team and company events, hackathons.
  • Education Package: Opportunities to boost your professional development with courses and trainings directly connected to your career goals 
  • Free of charge access to our EAP (Employee Assistance Program) which is a counseling service designed to support your mental health and well-being.
  • Skip writing cover letters. Tell us about your most passionate personal project, your desired salary and your earliest possible start date. We are looking forward to your application!We welcome applications from people who will contribute to the diversity of our company.

    We’re programmed to succeed

    See vacancies