Is Your Kotlin Code Really Obfuscated?

Featured in Android Weekly #402 and Kotlin Weekly #186
Each week I try to watch at least one session from the latest Google I/O or Android Dev Summit. I always learn something new, even with subjects that I am quite familiar with, and that is exactly why I keep watching year after year.
While watching a session on Java and Kotlin interoperability, I encountered something about Kotlin generated code and obfuscation that, as an experienced Kotlin developer, caught me off guard.
Every serious app should use some form of code shrinking and obfuscation tool. In Android, the common tools are ProGuard and more recently, R8.
These tools are fully supported by Kotlin without adding special rules (besides some special cases, which are not going to cover in this post)
This means that you can run ProGuard on your Kotlin app and everything should work as intended without crashing.
But is your Kotlin code really fully obfuscated?
With the following example, I will demonstrate that actually no, it does not.
Here is a simple class with:
- public function with non-null parameters
- public extension function
- public late init var
Let’s analyze the decompiled Java code the Kotlin compiler has produced for this class:
Notice that the generated code is calling a class called Intrinsics.
These calls are basically assertions of various Kotlin restrictions, like in this example, parameters should not be null or should be initialized before accessed.
Now, let’s build an optimized obfuscated APK (using ProGuard), tear it down (I’m using the awesome Bytecode Viewer for that) and analyze the code once again:
See the problem here?
The variables and extension method names are still present!
These names could expose our business logic to an outsider and this information may be used in a harmful way. It’s not a major security issue, but still a security issue.
Solution
ProGuard’s rules can be used for more than just preventing code from being obfuscated or deleted, it can be also be used to delete code.
Adding the following ProGuard rule to your proguard-rules.pro file will result in the removal of all the following method calls to the Intrinsics class.
-assumenosideeffects:
…In the optimization step, ProGuard can then remove calls to such methods, if it can determine that the return values aren’t used. Only applicable when optimizing.
In order to enable ProGuard optimizing, the default ProGuard file needs to be switched from proguard-android.txt to proguard-android-optimize.txt in the app’s build.gradle file.
Note that this step is not necessary when using R8.
Summary
Once the problem is known, the solution is pretty simple.
Moreover, once I searched this issue I found that it has been mentioned multiple times in other blog posts, but the lack of it in on the Kotlin official documentation is baffling.
These checks are unnecessarily lowering both security and performance, as such, they should be left out of release builds.
My suggestion is leaving these Intrinsics checks on debug builds in order to keep failing-fast if something is wrong, but removing them altogether from release builds.