In the world of mobile app development, the security of our applications is a priority… or at least it should be, depending on what we’re talking about.
Often, when implementing mechanisms like HMAC-SHA256 verification to protect communication or validate data, we overlook one very crucial part: where and how to store the secret key.
In this post, we will explain and provide a good example of what needs to be considered and how to handle secret keys in an Android application, especially when they are used to implement verification algorithms like HMAC-SHA256. While this measure may seem like a robust solution to ensure data integrity, storing the key within the application is a practice that carries significant risks.
For this case, we will use Adif as our example. Adif offers an Android application that lets you view real-time schedules and station information. It provides accurate and timely data about trains, with updates, etc. Now, you might be wondering, where does this data come from?
Adif has a “secret” API, well, “secret,” which requires authentication parameters to use it. It’s like a check to make sure you have permission to use the API and that you have no malicious intentions. The API expects us to send headers that we’re sending to the server but signed with an HMAC-SHA256 key. Why? Simple, this measure ensures that the API is not used by unauthorized people. Is it possible to use this API? Yes, although it’s a bit tedious and complicated, it’s possible, through reverse engineering.
Reverse Engineering
First, we need to understand how the application is sending requests. To do this, you can use an app like Reqable (requires a rooted phone) to monitor the requests the app is making.
The API expects something like this:
POST /portroyalmanager/secure/circulationpaths/departures/traffictype/ HTTP/2
host: circulacion.api.adif.es
user-key: f4ce9fbfa9d721e39b8984805901b5df
authorization: HMAC-SHA256 Credential=and20210615/20241029/AndroidElcanoApp/48875f934d7117ec/elcano_request,SignedHeaders=content-type;x-elcano-host;x-elcano-client;x-elcano-date;x-elcano-userid,Signature=e02df47713252d1d5b3b952d0b5c78c7a68c2043055c052d51d9d5e31820aa9b
x-elcano-client: AndroidElcanoApp
x-elcano-date: 20241029T180652Z
x-elcano-host: circulacion.api.adif.es
x-elcano-userid: 48875f934d7117ec
content-type: application/json;charset=utf-8
content-length: 126
accept-encoding: gzip
user-agent: okhttp/4.12.0
{"commercialService":"YES","commercialStopType":"YES","page":{"pageNumber":0},"stationCode":"64203","trafficType":"CERCANIAS"}
You probably don’t understand what’s happening here. Let’s explain it step by step.
This, in a more readable format, would look something like this. Imagine you are a journalist who needs updated information about the trains departing from a specific station to write an article or inform your readers. Let’s translate this technical request into an easy-to-understand metaphor:
Imagine you’re calling Adif’s train information office to ask about the Cercanías trains leaving from the Alfafar-Benetússer station (code “64203”). But because this office handles sensitive information, they need to make sure that you are who you say you are and that you have permission to access these data. So before responding, they ask for two things:
-
- A secret key: You provide them with a special signature (HMAC-SHA256) generated with a key that you shared with them previously. This ensures that you are indeed who you say you are because only you and Adif know that secret key.
-
- Personal identification: You provide them with your ID as a journalist (in this case, the
user-key
andx-elcano-userid
headers) and mention that you represent a specific media outlet (in this case, asx-elcano-client: AndroidElcanoApp
).
- Personal identification: You provide them with your ID as a journalist (in this case, the
Once Adif verifies your identity and the signature you sent, they process your request. In your message, you’re specifically asking for the following in plain language:
- “I want information about the Cercanías trains (trafficType: CERCANIAS) leaving from the Alfafar-Benetússer station (code 64203)”
- “I only care about trains that have commercial stops (commercialService: YES) and those that stop for passengers to board or disembark (commercialStopType: YES)”
- “Give me the data for the first page of results (pageNumber: 0)”
In response, Adif checks their database and returns the details of the trains that meet the criteria, such as schedules, destinations, and more.
Now, what happens if we have the wrong keys?
Adif will simply tell you that you’re not the journalist you claimed to be and will not provide you with any access.
With that in mind, let’s see how we can impersonate a real Adif user and send our own requests.
Decompiling the Android App
To understand how the HMAC generation is managed within an Android application, we first need to decompile it. This will allow us to analyze the code and see where the secret key is stored and how it is used. A useful tool for this is JADX, which converts APK files into a more readable format.
After several hours of investigation, the key is requested from a library with the function “getSecretKey()”. Since the data provided by JADX doesn’t show it, we need to take it a step further. We’ll have to hack the Android app to look more deeply into what’s happening. You might be wondering, how?
Frida
Frida is a very powerful dynamic instrumentation tool that allows you to analyze and modify applications in real-time, without needing access to the source code. Frida is mainly used for reverse engineering (reverse engineering) in mobile apps or desktop programs, allowing you to intercept and modify functions, method calls, or data at runtime.
How Does Frida Work?
Frida allows you to inject JavaScript code into an application during runtime. This enables you to “intercept” and modify the application’s behavior, inspect variables, and observe how critical operations like authentication or cryptography are handled.
After several hours of experimenting with various parameters, we finally obtained the encryption key. What now?
Now, we’ll need to replicate the Adif mechanism in our preferred programming language. Adif expects us to sign the X-Elcano
headers with this secret key and then convert it to hex. The final result is the authentication key.
Conclusion
In this article, we have explored how, despite the good intentions of implementing security using HMAC-SHA256, storing secret keys within an application is not a secure practice. Through a practical example with the Adif API, we saw how an attacker with the right tools can decompile the app, analyze the data flow, and gain access to the keys necessary to make authenticated requests.
The reverse engineering process with tools like JADX and Frida demonstrates that if keys are kept inside the app, they can be easily extracted, thus compromising the security of communication. Moreover, replicating this process in your own code could be as simple as extracting that key and signing the requests correctly.
For all these reasons, it is essential to adopt better security practices, such as storing secret keys in a more secure environment, like a server or a cryptographic key store, rather than within the app itself. By doing so, we ensure that even if the app is decompiled, attackers will not have access to critical information.
Finally, as developers, we must be aware that security is an ongoing process, and only through the implementation of best practices and regular audits can we adequately protect the data and integrity of our applications.