How safe is your tax rebate e-invoice app? Learn how to protect yourself.

More and more apps are available in Google Play Store allowing to manage your invoices. Some apps are focused on small businesses in order to make quotes or invoices for clients, but other apps are also targeting individuals. For instance in Portugal, the government is encouraging people to ask for invoices when buying products. These invoices can be then deduced in your tax form.

At Char49, we checked two of the most popular Android apps targeting the portuguese market. Combined them they have between 100.000 to 500.000 downloads according to Google Play Store.

These 2 apps benefit from the fact that the government does not provide an app for their own service.

Char49 performed an assessment on these 2 apps in order to check how the personal information (fiscal number, password, invoices, etc.) are handled. We focused in 3 main aspects:

    1. Data Storage
    1. Network Communication
    1. Debug/Log Messages


Data Storage


Storing sensitive information in smartphones is a hard task. Usually, it is not recommended to store credentials on the device. However, both apps give the possibility to store passwords locally.

Let’s start on the app where the credentials are just stored in plain text in the database. As shown, in the following code, the fiscal number (NIF) and the password are simply inserted in the SQLite database:

public static void insert(User user) {
SQLiteDatabase db =
ContentValues values = new ContentValues();
values.put("name", user.getName());
values.put("email", user.getEmail());
values.put(UserEntry.COLUMN_NIF, user.getTaxId());
values.put(UserEntry.COLUMN_PICTURE, user.getPicPath());
values.put(UserEntry.COLUMN_ADDRESS, user.getAddress());
values.put(UserEntry.COLUMN_COUNTRY, user.getCountry());
values.put(UserEntry.COLUMN_UPDATED, user.getUpdated());
values.put(UserEntry.COLUMN_PASSWORD, user.getPassword());
values.put(UserEntry.COLUMN_TUBPROCESS, user.getTubProcess());
long id = db.insert("user", null, values);
if (id > 0) {

Regarding the second app, it appears that the developers tried to obfuscate the credentials. Indeed, at first, we found the following functions:

private static String getPassword (Context context) {
rot13Decode (context.getSharedPreferences("any_prefnameefactura",
0).getString("taxadeiva", null));
private static String getNif (Context context) {
rot13Decode (context.getSharedPreferences("any_prefnameefactura",
0).getString("taxadeiva2", null));

At some point, the fiscal number (NIF) and the password were stored using the Shared Preferences API. In order to not store the credentials in plaintext, the developers used an old and well-know cipher called “ROT13”, which is a simple letter substitution cipher that replaces a letter with the 13th letter after it in the alphabet, i.e a A becomes a N.

However, we found that this code is not anymore used in the current version. The developers decided to move on and to store the credentials in an SQLite database. But this time, the password is encrypted using AES, symetric encryption mechanism.

In the following capture, you can see the following elements: ● contanif: Fiscal number in clear text, ● contapass: Password encrypted (note: the encrypted password is encoded in base64 in order to use printable characters), ● contanome: First and last name of the individual.

As we can expect, the key to encrypt and decrypt the password are hardcoded in the code as shown below (keys have been redacted):

public Boolean DecrypterSetup(Context context) throws Exception {
byte[] initializationVector = new byte[16];
this.secretKySpec = new
SecretKeySpec(SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"). generateSe
cret(new PBEKeySpec("yXXXXXXX".toCharArray(), "42XXXXXX".getBytes(), 1024,
256)).getEncoded(), "AES");
this.cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
SharedPreferences settingsSharedPref =
context.getSharedPreferences("any_prefnameefactura", 0);
String strIV = settingsSharedPref.getString("IVect", "");
if (strIV.length() == 0) {
new Random().nextBytes(initializationVector);
Editor editSharedPref = settingsSharedPref.edit();
Base64.encodeToString(initializationVector, 0));
if (!editSharedPref.commit()) {
return Boolean.valueOf(false);
initializationVector = Base64.decode(strIV, 0);
this.ivParmSpec = new IvParameterSpec(initializationVector);
return Boolean.valueOf(true);

In the previous extract, you can also identify that the initialization vector is stored in a Shared Preferences file.



Regarding how invoices are stored, both apps store them in a SQLite database without any protection. One of the apps is storing very detailed information for each invoice as shown below:

As you can see, you can retrieve the following elements: ● Merchant fiscal number (nifemitente), ● Merchant name (nomeemitente), ● Invoice unique identifier (numerodocumento), ● Customer fiscal number (nifadquirente),

Comparing to the other app tested, the same kind of information is also stored but with less details.


Network Communication

In a standard mobile application assessment, we look for unencrypted communications to external server. However, in our case, both apps have to communicate with the following portuguese websites: ● ● ●

These websites are only available using TLS communication (HTTPS). So, we focused our analysis on insecure configuration or implementation.

One of the apps, they added an extra security layer with the implementation of a technique called “Certificate Pinning”. In order to ensure that the app is connecting to the legitimate servers, the app keeps a fingerprint from the TLS certificate provided by the websites. The fingerprint of the previous websites is defined inside the application on the following class:

public final class xxKeyManager extends CertPinManager {
protected Map<String, String> getPins() {
Map<String, String> map = new HashMap();
return map;

And then, we found that this class is used to authenticate the user or retrieve data from the government websites as shown below:

TrustManager[] tm = new TrustManager[]{new
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, tm, null);
SSLSocketFactory ssFactory = new
String csrf = getCSRF(ssFactory);
if (csrf == null || isCancelled()) {
return null;
Map<String, String> result = authenticate(user,
csrf, ssFactory);

Regarding the other app, there is no Certificate Pinning implemented but the TLS certificate is correctly checked. However, during our investigation, we found that the app is connecting to an external server using HTTP protocol: ●

The performed requests are limited but seems to be used in order to obtain details about a merchant. The app sends a list of merchant fiscal numbers and the server replies with some information such as merchant name, merchant address, merchant category, etc.

Here is a request performed by the app:

An attacker by sniffing the network is able to identify which merchants used to be frequented by the user.


Debug/Log Messages

A most common mistake performed by developers is to let debug messages inside production code. Sometimes these debug messages can leak sensitive information.

In the case of one of the apps, we found that all the requests performed against the government websites are logged. In addition, the full content of the request is displayed, which allows to retrieve the credentials of the user when the authentication request is performed, as shown below:



Keeping sensitive information securely in Android devices is a hard task. In addition, developers need to keep in mind which risk scenarios could be used against their app (theft, rooted device, usb debugging enabled, etc.).

Regarding the data manipulated by the app, some choices should be made.

Concerning passwords, we strongly recommend to NOT store them in the device. If the device is stolen, an attacker could retrieve it and use it against the service. In a worst scenario, if the user shares this password to other services, the impact could be important. If it is really needed to store passwords, the Android Keystore should be used, but it is only available in recent Android versions.

Concerning personal information, we recommend to store them in an encrypted SQLite database. The password should be derived from a PIN or a password asked to the user.

Network communication should be always enforced encryption (TLS) to ensure confidentiality and integrity. Developers should consider the network as hostile. Implementing Certificate Pinning is a very good security level. However, the most important is to ensure that the TLS certificate from the server is correctly checked and only strong ciphers are allowed. In many assessments, we found developers disabling TLS certificate checking during the development phase and then forgetting to re-enable it in production.

Debug and log messages should be disabled in production environment in order to avoid leaking sensitive information.

Finally, during the assessment of these apps we didn’t find malicious behavior, for instance the app trying to steal credentials or personal information. The main mistakes found here are mainly due to a lack of security and privacy awareness.

Char49 recommend end users to be careful when installing those kind of apps and mainly what kind of data you are allowing the app to access. As security practices, we recommend to:

  • Check the permissions asked by the app (the less, the better) and accept only the ones that are truly necessary,
  • Not use the “Remember password” in the app,
  • Avoid using apps manipulating sensitive information in public Wifi or not trusted networks,
  • Use a strong password to lock your smartphone (at least 6 characters),
  • Not let your phone unattended.

Share this Post