I have been playing around with an Android app as a side hobby for a while now. It is a simple game app, which can load new game boards from a server using HTTP GET. So the data does not contain any personal or business critical data.
But since I like to (at least try to) implement things well and learn more in my toy projects, I decided to protect the game board directory in the server side using .htaccess and to move to using HTTPS. This way the authentication HTTP headers containing the user name and password in the request are encrypted, protecting login info while it is transmitted over the network.
HttpsURLConnection urlConnection = null; String[] games = null; try { URL serverUrl = new URL(serverAddress + scriptName); urlConnection = (HttpsURLConnection) serverUrl.openConnection(); String authString = getUserNameAndPassword(); String authEncodedString = Base64.getEncoder().encodeToString(authString.getBytes()); urlConnection.setRequestProperty("authorization", "Basic " + authEncodedString); response = urlConnection.getResponseCode(); if (response == HttpURLConnection.HTTP_OK) { // ....
The method getUserNameAndPassword() returns a string username:hashed-password, used in the authentication of the client.
Since using HTTPS, that information is encrypted while transmitted. But what about in the actual application binary? If you store the username and password as plain strings in the code, it would be relatively easy by investigating the binary to dig out this data and use it for unauthorised access to the server. In my app, that wouldn’t be such an issue, since the server side code only supports HTTPS GET to list the available game board files and there is nothing else you could do there.
Anyways, I decided to find a way to try to make it a bit more difficult for anyone to dig out authentication strings from the client binary. I searched for possible solutions, of which this article is a good summary.
In my app, I decided not to use any proxies or cloud services, since all that trouble is not worth it, considering the nature of data my app uses. There is no perfect way to do this since any method has their weaknesses. I picked one simple way to do it to learn how it would actually work in my case.
What I decided to do was this:
- First and most obviously, I used long and cryptic username and password values when creating the username and password for .htaccess.
- I made sure that the .password file is not in the server directories the HTTP server could read, thus inaccessible from the network using HTTP. This is also something very obvious one should always do.
- I took the generated and hashed password, and further obfuscated it and the username using (for example) xor and then rot13. To do this, I implemented a small tool app which takes in the username:hashed-password String and outputs the obfuscated byte array from this step. Xor nor rot13 are not very efficient encryption methods, but still manage to make finding out the passwords a bit more difficult. When doing the xor, I use a byte array, not a String. You could add even more steps here, using other simple additional obfuscation methods, as long as you make sure that you can reverse the process and arrive — using the exact opposite steps — to the original username and password.
- I placed the obfuscated username and password bytes in the client app source code not in a String but in a byte array in the client code. So anyone looking for username or password Strings from the code is not going to find it. Obviously the bytes used in xor step need to be in the client code too.
- I further divided the byte arrays from step 4 into 2-3 different byte arrays in different places in the code, so that they are not in a continuous area of memory. At this point you could also e.g. reverse bytes in some of the byte arrays to further confuse the possible investigator of the app binary.
So, when the client code is then executed, the divided byte arrays are (some possibly reversed; step #5) combined, then a String is created from them at run time. After this, the obfuscation steps from #3 above are executed in reverse order (e.g. first rot13 then xor), producing the original username and the hashed password. This is then put in the authorization header of the HTTPS request.
Though not perfect, this is in my applications case a good enough security solution. Obviously you should not publicly share which substitution ciphers or other methods (xor, rot13, reversing, …) you use in obfuscation.
Below is the output from the tool to create obfuscated authorisation bytes.
Original string: demousername:hashed-demo-password-from-htpasswd-tool
Rotted: qrzbhfreanzr:unfurq-qrzb-cnffjbeq-sebz-ugcnffjq-gbby
encoded: EBVOVgALBQATDwkTXUFaDhgFFF8QARsFGVcGCxEPEAQCTBRRVhJAAgIRDxUHDUUZDw8VHA==
Take these bytes to a byte array in client app:
{69,66,86,79,86,103,65,76,66,81,65,84,68,119,107,84,88,85,70,97,68,104,103,70,70,70,56,81,65,82,115,70,71,86,99,71,67,120,69,80,69,65,81,67,84,66,82,82,86,104,74,65,65,103,73,82,68,120,85,72,68,85,85,90,68,119,56,86,72,65,61,61}
With this key:
{97,103,52,52,104,109,119,101,114,97,115}