Blog Infos
Author
Published
Topics
Published

πŸ‘‹ Hi and welcome to the second post in this series where we deep-dive into Android Security. This series focuses on theΒ Top 10 MobileΒ security threats as determined byΒ The Open Web Application Security Project (OWASP) Foundation, the leading application security community in our field.

Before checking this post, please consider checking out the previous one β€˜Improper Platform Usage’ which is available on my site, and onΒ ProAndroidDev.

⚠️ Please note that this series is for educational purposesΒ only. Remember to only test on apps where you have permission to do so and most of all,Β don’t be evil.

Finally, if you enjoy this series or have any feedback,Β please drop me a message. Thanks!

Introduction

In this second helping of my series on Android Security, we shall take a look into the #2 threat to mobile application security as determined by OWASP, β€œInsecure Data Storage”.

When it comes to data storage and Android, there are a number of solutions that may immediately jump to mind.

In this blog, we will look at the three most common approaches you’ll already likely be using in your apps:

  1. SharedPreferences
  2. Room databases
  3. Jetpack’sΒ DataStore

As app developers, the need for storing data is an extremely common scenario that we face. However, it is also important to understand the security concerns that this introduces. As we shall see, it is often trivial for malicious actors to access stored data on the device and compromise it.

It is also worth noting before we jump in, that it is a best practice toΒ avoidΒ storing any form of sensitive data on a device. Sensitive data may include a user’s personally identifiable information (PII), your API keys or any other type of data that may be β€˜dangerous’ if it fell into the wrong hands. I would always recommend that, whenever possible, sensitive data should be stored remotely and only accessed by your app when it is required.

Let’s kick things off by looking at our ever-faithful companionΒ SharedPreferences

Shared Preferences

TheΒ SharedPreferencesΒ API has been a staple of Android Development since the very beginning, making its debut on the platform in API 1. It’s a tried and tested quick solution for developers to store data in a key-value pair (KVP), however, it is not without its flaws.

Let’s look at a very simple example:

val prefs = context?.getSharedPreferences("mySharedPrefFile", Context.MODE_PRIVATE) ?: return
// Store some credentials we might not want others to read
prefs.edit().putString("mySecretKey", "mySecretValue").apply()
view raw Example1.kt hosted with ❤ by GitHub

Behind the scenes, as a specific preference file name was givenΒΉ, an XML file with this name is created (if required) within the device’sΒ /data/data/{package name}/shared_prefsΒ folder.

Next, the preferences to be stored are aggregated and then accessed via an internal reference to aΒ Map<String, Any?>Β² which then has its contents written to the XML file through the use of aΒ TypedXmlSerializer, thus saving the KVPs for read/write requests in future.

Through the use ofΒ adbΒ let’s look and see what this output does on a device with aΒ debuggableΒ build:

$ adb shell
​
# Run as your package name
$ run-as dev.spght.example
​
# Show your app's local folders
$ ls
cache  code_cache  shared_prefs
​
# Show what is in the shared_prefs folder
$ ls shared_prefs
mySharedPrefFile.xml
​
# Print out the contents of the file
$ cat shared_prefs/mySharedPrefFile.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="mySecretKey">mySecretValue</string>
</map>

As you can see, the β€œmySecretKey” preference is stored in plaintext, free for all to view. However, attempting this on a no-debuggable release build will result in an errorΒ package not debuggable. So, that’s secure right?Β Wrong.

⚠️ Android Backup Exploitation

It isΒ stillΒ possible to access the shared preferences of a production app on a device through the β€˜misuse’ of theΒ adb backupΒ command. This command’s intended purpose is to allow for an archive of a device or specific app to be created and then later restored to a device through the use ofΒ adb restore, However, the backup archive is itself aΒ tarΒ file that can be extracted to read the contents of the private app data, including any shared preference XML files.

The process of completing this is fairly straightforward and through the use of an external tool, known as theΒ Android Backup Extractor, this process can yield the hidden contents of an app extremely easily.

# Make a backup file for app 
$ adb backup -noapk -f backup.ab dev.spght.example.prod
WARNING: adb backup is deprecated and may be removed in a future release
Now unlock your device and confirm the backup operation...
​
# Use ABE to unpack the backup file (12345 is the password as set through the device)
$ java -jar abe.jar unpack backup.ab output.tar 12345
Calculated MK checksum (use UTF-8: true): 739E00581D0B3EB5A17F3E1A43D21F561BA8E8C1CA35C48E636495E9C57EF0A1
31% 63% 67%
4096 bytes written to output.tar.
​
# Extract the unpacked backup
$ tar xvf output.tar
x apps/dev.spght.example.prod/_manifest
x apps/dev.spght.example.prod/sp/example.xml
​
# View the extracted shared preference file
$ cat apps/dev.spght.example.prod/sp/example.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="mySecretKey">mySecretValue</string>
</map>

Voila! We have read the contents of a non-debuggable app’s preferences.

It should be noted that this method is only available to exploit apps thatΒ do notΒ explicitly setΒ android:allowBackup="false"Β within their manifest file. If your app does not need to handle any form of backup during a device upgrade, it is highly recommended you set this option within your manifest to avoid this potential hazard.

Additionally, Google has recently acknowledged this vulnerability and, since Android 12, has implemented the following restrictions:

To help protect private app data, Android 12 changes the default behavior of theΒ adb backupΒ command. For apps that target Android 12 (API level 31) or higher, when a user runs theΒ adb backupΒ command, app data is excluded from any other system data that is exported from the device.

This is very good news. Simply through the targeting of an SDK, your non-debuggable apps should be well protected from just about anyone snooping on your preferences. However, can we take this even further?

Encrypting Shared Preferences

I am hopeful that by now you can see the dangers of storing PII or other sensitive data throughΒ SharedPreferences. However, should your app require a backup strategy, not target API level 31+ or you are (rightly) concerned about your mobile app’s security, you may wish to encryptΒ anyΒ data you save within your shared preferences to ensure data cannot be easily read.

This is not something thatΒ SharedPreferencesΒ does out the box, but thankfullyΒ Jetpack SecurityΒ (known as JetSec) can provide this functionality through it’sΒ androidx.security:security-cryptoΒ library, giving developers access to anΒ EncryptedSharedPreferencesΒ class that wraps the existingΒ SharedPreferencesΒ API.

Note:Β While the stable 1.0.0 release ofΒ androidx.security:security-cryptoΒ supports API 23+, a back-port to API 21 is part of the upcoming 1.1.0 release and is currently available in early alpha versionsΒ³.

A basic example of using JetSec to secure shared prefs can be seen here

val masterKey = MasterKey.Builder(this)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
EncryptedSharedPreferences.create(this, "myEncryptedPrefsFile", masterKey, PrefKeyEncryptionScheme.AES256_SIV, PrefValueEncryptionScheme.AES256_GCM).edit {
putString("mySecretKey", "mySecretValue")
}
view raw Example2.kt hosted with ❤ by GitHub

I highly recommend checking out theΒ Google blog postΒ that goes into further detail on how the library can be used and configured for your own app’s security needs. In particular, it is worth making note of the methods highlighted by the JetSec team that enable extra layers of security, including forcing cryptographic operations to be performed on a dedicated hardware security module or requiring user authentication before preferences can be accessed.

Important options:

userAuthenticationRequired()Β andΒ userAuthenticationValiditySeconds()Β can be used to create a time-bound key. Time-bound keys require authorization usingΒ BiometricPromptΒ for both encryption and decryption of symmetric keys.

unlockedDeviceRequired()Β sets a flag that helps ensure key access cannot happen if the device is not unlocked. This flag is available on Android Pie and higher.

UseΒ setIsStrongBoxBacked(), to run crypto operations on a stronger separate chip. This has a slight performance impact, but is more secure. It’s available on some devices that run Android Pie or higher.

But how is this actually working behind the scenes? The cryptography used by JetSec is handled by Google’sΒ tink library, an open-source project that provides industry-leading crypto algorithms and helps developers follow best practices when performing cryptography. AsΒ EncryptedSharedPreferencesΒ is just an implementation ofΒ SharedPreferences, it can utilise Tink internally and provide Tink’s functionality when handling preferences.

Let’s take a look at the XML output from using JetSec

<map>
<string name="ARTYCGdkOdwAqjLCjWdsepYfbO+lJzJFFrHIta8JSE0=">ASTonpk6n1buL/VN6mB/S95HNcyHzvFp5qbcpkJMjSQbqRkzO3HWe5KKcP6eTwtzIFamU3Ag</string>
<string name="__androidx_security_crypto_encrypted_prefs_key_keyset__">12a90155259183605a12481ccf406838afc98126862109cc5e083185fe7259a052ccb4d1f859ac62ee1ab624f4b35df36c53a23c24547ee322aacc4526a654fd99e9997c0e6bf389b3bf2706a2e29b63d99a1e74535d68457fda16f04e706f127ca09c01622e26db72339720e814af1d6533efc8705eb8a073fa4e5afb2dbfb9446eaa27801942e0c8a8462ff6aed86738f500cdb77655294549810c2cfbe011b4c3900e3504eb3947502c7c1a4408e790e0a601123c0a30747970652e676f6f676c65617069732e636f6d2f676f6f676c652e63727970746f2e74696e6b2e4165735369764b6579100118e790e0a6012001</string>
<string name="__androidx_security_crypto_encrypted_prefs_value_keyset__">128801700a3e737db7b3f8385be4aea6f74fbb844b86532055fa99032c67df4c5a5543e799dd9a621013ba716749b3decef994896914cea1d8dbf25fc13e6a7c6f19a0488dbd4a339642f4bfc4ad82fe1b27b4d4ca0a79c55e57354389f7c2e115af8c0d6f8fff0093299c2481a6cf25b5444beb614c227a03c64e1262ecc0a137a1273df34ae24d78ddd51a440899bda2a702123c0a30747970652e676f6f676c65617069732e636f6d2f676f6f676c652e63727970746f2e74696e6b2e41657347636d4b657910011899bda2a7022001</string>
</map>
view raw Example3.xml hosted with ❤ by GitHub

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Migrating to Jetpack Compose – an interop love story

Most of you are familiar with Jetpack Compose and its benefits. If you’re able to start anew and create a Compose-only app, you’re on the right track. But this talk might not be for you…
Watch Video

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer for Jetpack Compose
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engin ...
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer f ...
Google

Jobs

✨ Much better!

Room Databases

Next let’s look at Room databases, a popular choice in the Android ecosystem for storing large amounts of structured data. Room is an abstraction layer over the database engine SQLite, allowing for developers to easily create tables, write queries and auto-magically handle the standard CRUD operations for databases.

Using the Room documentation’s basic setupΒ example, let’s assume a production application created a very simplistic database namedΒ insecure-databaseΒ that contains user data, such as their user id, first and last name. Certainly, something you might not want to be sharing inadvertently.

As we have already discovered with shared preferences, the Android backup process can once again be used to extract private app data. Following the same process we used previously, an app’s Room SQLite databases can be found within theΒ {package name}/db.

UsingΒ sqlite3Β on the command line, we can inspect the database, list the tables and show table data including the contents.

$ sqlite3 insecure-database
​
sqlite> .tables
User    android_metadata    room_master_table
​
sqlite> .schema User
CREATE TABLE `User` (`uid` INTEGER NOT NULL, `first_name` TEXT, `last_name` TEXT, PRIMARY KEY(`uid`));
​
sqlite> SELECT * FROM User LIMIT 5;
1653992407422|First|Last
1653992407614|First|Last
1653992407658|First|Last
1653992407685|First|Last
1653992407686|First|Last

Should an app be storing any sensitive data in a database without proper encryption, it is (just like shared preferences) available to inspect in plaintext. Boo πŸ‘Ž

Encrypting Room

So, how can we fix this? Thankfully the well-known (and trusted)Β SQLCipher projectΒ provides anΒ Android LibraryΒ to support the transparent 256-bit AES encryption of database files.

This can be added as a dependency viaΒ net.zetetic:android-database-sqlcipher:{latest version}. However, to use SQLCipher when creating your Room instance, supply the builder with SQLCipher’s implementation ofΒ SupportSQLiteOpenHelper.Factory, aptly namedΒ SupportFactory.

// Use a user-entered passphrase to encrypt/decrypt
val passPhrase: ByteArray = "password".encodeToByteArray()
val sqlCipherSupportFactory: SupportSQLiteOpenHelper.Factory = SupportFactory(passPhrase)
val database = Room.databaseBuilder(
applicationContext,
YourRoomDatabase::class.java,
"secure-database")
.openHelperFactory(sqlCipherSupportFactory)
.build()
view raw Example4.kt hosted with ❤ by GitHub

Behind the scenes, on initialisation SQLCipher uses the supplied β€˜passphrase’ to generate aΒ uniqueΒ key for the AES encryption/decryption of the database file. This is explained in further detail within the SQLCipherΒ docsΒ and paraphrased below.

When initialized with a passphrase SQLCipher derives the key data using PBKDF2-HMAC-SHA512. Each database is initialized with a unique random salt in the first 16 bytes of the file.Β This salt is used for key derivation and it ensures that even if two databases are created using the same password, they will not have the same encryption key.Β The default configuration uses 256,000 iterations for key derivation.

To verify this is working as expected, we can install sqlcipher⁴ and open the secure database. Using PRAGMA key with the same passcode as provided in your code, it is possible to decrypt the database and read the contents in plaintext once again.

$ sqlcipher secure-database
​
sqlite> .tables
Error: file is not a database
​
sqlite> PRAGMA key = 'password';
ok
​
sqlite> .tables
User    room_master_table
​
sqlite> SELECT * FROM User LIMIT 5;
1653992407422|First|Last
1653992407614|First|Last
1653992407658|First|Last
1653992407685|First|Last
1653992407686|First|Last

Voila. Once again, we find ourselves having stored our data securely with minimal development effort!

DataStore

Finally, let’s look atΒ DataStoreΒ the relative newcomer of data storage within the Android ecosystem. Its Kotlin-first approach and utilization of coroutines to provide asynchronous reading or writing of preferences make it an attractive option for modern Android development. However, once again, it is potentially vulnerable to being read via theΒ adb backupΒ method 😬

Let’s take another simple example

val Context.dataStore by preferencesDataStore(name = "insecure-data-store")
val pref1 = stringPreferencesKey("example_pref")
val pref2 = stringPreferencesKey("example_pref_2")
dataStore.edit { settings ->
settings[pref1] = "My 1st Pref"
settings[pref2] = "My 2nd Pref"
}
view raw Example5.kt hosted with ❤ by GitHub

Using the backup method, once extracted we find our data withinΒ datastore/insecure-data-store.preferences_pb. This file may look slightly odd at first glance, but by using Google’s protobuf library⁡, we can inspect the contents of the file and read the saved preferences.

$ hexdump -C insecure-data-store.preferences_pb
00000000  0a 1d 0a 0c 65 78 61 6d  70 6c 65 5f 70 72 65 66  |....example_pref|
00000010  12 0d 2a 0b 4d 79 20 31  73 74 20 50 72 65 66 0a  |..*.My 1st Pref.|
00000020  1f 0a 0e 65 78 61 6d 70  6c 65 5f 70 72 65 66 5f  |...example_pref_|
00000030  32 12 0d 2a 0b 4d 79 20  32 6e 64 20 50 72 65 66  |2..*.My 2nd Pref|
00000040
​
$ protoc --decode_raw < insecure-data-store.preferences_pb
1 {
  1: "example_pref"
  2 {
    5: "My 1st Pref"
  }
}
1 {
  1: "example_pref_2"
  2 {
    5: "My 2nd Pref"
  }
}

πŸ˜… I mean, what else did we expect at this point!

Encrypting DataStore

As of the time of writing, we don’t have anyΒ official supportΒ for encryption from the DataStore or JetSec library, however, we may not have long to wait πŸ‘€

​In the meantime, an open-source solution exists inΒ encrypted-datastore, a library that wraps DataStore and utilises Google’sΒ tink libraryΒ in a similar fashion to that of JetSec’sΒ EncryptedSharedPreferences. However, please only use this with extreme caution in your projects as this library is maintained by an individual and not a well-known trusted entity.

Let’s hope we see an official solution very soon!

Next up πŸš€

In the upcoming posts within this series, we shall explore more of the OWASP Top 10 for Mobile. Next up is #3Β Insecure Communication.

Thanks 🌟

Thanks as always for reading! I hope you found this post interesting, please feel free to tweet me with any feedback atΒ @Sp4ghettiCodeΒ and don’t forget to clap, like, tweet, share, star etc

Further Reading
Footnotes

[1] TheΒ getPreferences(int mode)Β method withinΒ ActivityΒ will use the activity’s name as the file name by default

[2] The implementation ofΒ SharedPreferencesΒ is in Java, soΒ Map<String, Object>Β is the true typing

[3] It is worth noting that it seems likeΒ androidx.security:security-crypto-ktx:1.1.0-alpha03Β does not correctly support a minimum version of API 21 and remains at API 23+ – I raised a bug report for thisΒ here

[4] UsingΒ brew install sqlcipherΒ on macOS – check online for other OS install methods

[5] UsingΒ brew install protobufΒ on macOS – check online for other OS install methods

This article was originally published onΒ proandroiddev.com on June 05, 2022

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
πŸ‘‹ Hi and welcome to the third post in this series where we deep-dive…
READ MORE
blog
Protect your user’s privacy and adhere to possible technical requirements
READ MORE
blog
Security and privacy are the two most talked about topics these days. Like any…
READ MORE
blog
πŸ‘‹ Hi and welcome to a new series of blog posts in which we…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu