Reverse Engineering Ghanapostgps Mobile Application To Create An Api For Local Developers
GhanaPostGPS is Ghana’s official digital addressing system which ensures that all locations in the country are addressed. Each location has a unique address which is a composite of the postcode (region, district & area code). Its core functionality is to translate a user’s GPS location coordinates to a user-friendly digital address. Those who developed the addressing system did not make available a public application programming interface (API) that can be used by local developers in building technological solutions. This research presents how the digital addressing system’s mobile application was reversed engineered to create a publicly available API (https://jayluxferro.github.io/post/ghpgps/).
Introduction
Digital addressing is a modern and revolutionary approach to allocating addresses within a defined space with the aid of the latest geocoding technology. Ghana, in its effort of enhancing digitization, has introduced this digital addressing system (GhanaPostGPS) which covers every inch of the country and ensures that all locations in the country are addressed [1]. With GhanaPostGPS, every location has a unique digital address. It is rather unfortunate that digital solutions built in the country do not make use of this addressing system since there isn’t a publicly available API; hence the essence of this research.
How does the GhanaPostGPS Application work?
The communication process follows a client-server architecture (as shown in the figure below). The application runs on both mobile (iOS and Android) and web platforms.
When a user launches the application, he has the option to sign in (if he’s an existing using), sign up as a new user or use the app without registering (anonymous account). Phone numbers are used in signing on to the platform and verified through a short message service (SMS). When a user signs in successfully, he has the ability to register/generate a digital address and also query for location details (either by name or by address). The mobile applications (iOS and Android) have panic buttons which help users in easy communication with the Police, Fire and Ambulance services in the event of an imminent danger. This is made possible due to the integration of a session initiation protocol (SIP)[2] API in the mobile clients.
End-to-End Encryption (E2EE)
Communication between the clients and the web service uses asymmetric encryption. When the application is launched for the first time, the mobile client connects to the endpoint https://api.ghanapostgps.com/GetAPIData.aspx to get the server’s public key (a sample is shown below).
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlO6O2gAGlT4+YP+evP9c
9qynWdv/qIAx5Jc4kp+UTmrsn8wJn4bD9H8rynSvepH0navZiDwYvioAPbIcR6cG
MMFnP5/2wN9zrBFZtnofcpSrk4q9/GRHj4IuHheQjvMiislrRdIEgqxjMQ1aaiG7
+MeoeQuHz08O+aecHuMtJTXzcIQDqkMHkeA/yt/ge/ASDqSRn0Hdpa/4OA/ZtVpT
8Ph2lLgMv+O5Iz11UIwSqyewSdAZzX0H4jUPKCCfnhgWsS+7WJU6KufYptvl0/P4
NSdJKSdYg/y44pWiPxlgMUf6s1nOXJJ0vSi0zrDFjx+y+GD2h+dMBRWe9nym+NmJ
1QIDAQAB
-----END PUBLIC KEY-----
Upon getting the public key, the mobile client generates a random string (using the method below) of length 16 characters.
private static final String ALLOWED_CHARACTERS = "0123456789qwertyuiopasdfghjklzxcvbnm!@$#^&*()";
public static String getRandomString(int i) {
Random random = new Random();
StringBuilder sb = new StringBuilder(i);
for (int i2 = 0; i2 < i; i2++) {
sb.append(ALLOWED_CHARACTERS.charAt(random.nextInt(ALLOWED_CHARACTERS.length())));
}
return sb.toString();
}
The generated random string becomes a symmetric key that is used in the encryption and decryption of data between the client and the server. The client sends the generated random string (16 bytes) together with a universally unique identifier (UUID) and the operating system’s name (Android or iOS) to the server. This data is encrypted (using RSA) and encoded in base64 using the server’s public key before it is sent. A sample is shown below:
TiJ/O4d2rFzaR046lLMYBJ6yU3e+vqDjkrYNVrhFm9K+jLXbMzTB6xAdtz/f/Rx+Nyw5ZB64ok3v8MRJq9jf8NwpYeFQZUGR0UzMmg
EgYR3MlAgFz7vRkQt0GGt/BwEaK081PJKxnqVqqXjr3NqNbfJr3GDDkfIfVKT4xOZRZbcCFdPpDD6Ofb5RD7mL8LQLvwOPOUVq3+/
MlNDVhxOD4Osq0PqWh8CvrZY8y2Q1sDJYDTUsKFn0ChxFNtJhso1ImtqLBkNFZbrWUXn6NbHV+p3HBJVeZNcJxlWPPHaBh8Ip7
qPOnnMww4ZXWC88/tWTlScFemwTcyGpT58T9rMySA==
The server responds by sending a base64 encoded data which can be decrypted using the client’s symmetric key. The decrypted data is shown below.
PlsUseYourOwnKey||https://api.ghanapostgps.com/PublicGPGPSAPI.aspx
The decrypted data contained a new endpoint https://api.ghanapostgps.com/PublicGPGPSAPI.aspx through which all subsequent communications were made. The symmetric encryption used is advanced encryption standard (AES); as shown below.
// Java, AES CBC Encryption
package com.ghanapostgps.ghanapost.util;
import android.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.compress.utils.CharsetNames;
public class AESenc {
private String key = "";
public AESenc(String str) {
this.key = str;
}
public String encrypt(String str) {
try {
byte[] bytes = Utils.getRandomString(16).getBytes(CharsetNames.UTF_8);
IvParameterSpec ivParameterSpec = new IvParameterSpec(bytes);
SecretKeySpec secretKeySpec = new SecretKeySpec(this.key.getBytes(CharsetNames.UTF_8), "AES");
Cipher instance = Cipher.getInstance("AES/CBC/PKCS7PADDING");
instance.init(1, secretKeySpec, ivParameterSpec);
byte[] doFinal = instance.doFinal(str.getBytes());
byte[] bArr = new byte[(bytes.length + doFinal.length)];
System.arraycopy(bytes, 0, bArr, 0, bytes.length);
System.arraycopy(doFinal, 0, bArr, 16, doFinal.length);
return Base64.encodeToString(bArr, 0);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public String decrypt(String str) {
try {
byte[] decode = Base64.decode(str, 0);
int length = decode.length - 16;
byte[] bArr = new byte[length];
byte[] bArr2 = new byte[16];
System.arraycopy(decode, 0, bArr2, 0, 16);
System.arraycopy(decode, 16, bArr, 0, length);
IvParameterSpec ivParameterSpec = new IvParameterSpec(bArr2);
SecretKeySpec secretKeySpec = new SecretKeySpec(this.key.getBytes(CharsetNames.UTF_8), "AES");
Cipher instance = Cipher.getInstance("AES/CBC/PKCS5PADDING");
instance.init(2, secretKeySpec, ivParameterSpec);
return new String(instance.doFinal(bArr));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
From the AES encryption/decryption method, the initializing vector (IV) is the first 16 bytes of the base64 decoded data. When the client is encrypting a piece of data to be sent to the server, it generates a 16 byte string based on the getRandomString method; this forms the first 16 bytes of the data to be sent. The actual data is encrypted using the symmetric key and the randomly generated 16 bytes string (the IV). Afterwards, the data is base64 encoded before it is sent.
The client decrypts the information received from the server by decoding the base64 string. It then extracts the first 16 bytes as the IV. The remaining bytes of data is decrypted using the symmetric key and the IV.
The same E2EE mechanism was implemented in Golang which was used in developing the public API. Details of the implementation can be found using https://github.com/jayluxferro/ghanapostgps.
References
- GhanaPostGPS, https://ghanapostgps.com.
- Session Initiation Protocol (SIP), https://tools.ietf.org/html/rfc3261.
- Universally Unique IDentifier (UUID), https://tools.ietf.org/html/rfc4122.