Chapters

Hide chapters

Android App Distribution

First Edition · Android 12 · Kotlin 1.5 · Android Studio Bumblebee

6. Security Best Practices
Written by Fuad Kamal

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Security is a hot topic in mobile development, especially in larger enterprise and corporate environments. Many companies pay top dollar for security experts to run penetration tests and other security tests on their apps. Some companies license scanning tools, such as Veracode. There’s even a relatively new career path for people who have cross-domain knowledge in mobile development and security.

However, security starts with the architecture and code you write to develop your app. Following security best practices ensures your users’ safety, which is the ultimate goal. In this chapter, you’ll examine some of the techniques and best practices for securing and hardening your app.

Data storage domains

Securing user data is of paramount importance in mobile apps, both on-device and in transit on networks. Protecting access to source code, API endpoints and keys is a top security concern for corporations.

But for your users, the most critical security concern is protecting their data. You need to protect their data when transmitted to or from the app, which falls under networking. HTTPS (SSL) handles Network traffic.

You also need to protect data stored locally on mobile devices, which falls under data storage. On-device, encryption is the primary way to secure data. Since Android 5.0, the OS encrypts the contents of the user’s data partition by default.

But sometimes, you want to provide an extra layer of protection for sensitive data. For example, you may want extra protection when using shared storage and handling sensitive information, such as personally identifiable information (PII), financial records or any especially sensitive data. Also, starting with Android 10, full disk encryption is no longer an option, and your app must do some form of file encryption to secure sensitive data written to files.

In Android, there are three domains concerning data storage:

  1. Internal storage is the default storage mechanism and has built-in encryption provided by the system.

  2. You can also store files on external storage, such as SD Cards or even internal disk space your app considers external. If you need to support this type of storage and the data is sensitive, use the Jetpack Security Library. You’ll read about it in more detail later in this chapter.

  3. Finally, content providers are an encapsulation method for data storage that provides mechanisms for apps to manage private, self-only access or access to data provided by other apps, such as getting information from Contacts or Gmail, and for sharing data with other apps. Even if your app doesn’t share data with other apps, you might use content providers to benefit from the abstraction layer. However, if you do, make sure you disallow access to your app’s content providers by setting android:exported=false in your app manifest file for the content provider.

Securely storing data

In the past, learning how to encrypt data on Android often meant long hours spent searching the web, and much of what you found was outdated or incorrect. Fortunately, now you can leverage Jetpack Security to easily add an extra layer of security and data protection to your apps.

Securing the Organized Simple Note app

Open the starter project for this chapter and build and run. Running the project for this chapter is best done on an Android device and not on an Android emulator due to the use of device hardware for encryption.

The SimpleNote App Interface
Zpo QahkseTavo Ifb Upxadteso

The SimpleNote App Interface
Hvi QihnnaGune Itd Apgepnowe

Data encryption: Encrypting files and shared preferences

To protect your users’ sensitive data, you should encrypt shared preferences as well as files that may contain such data. Just as with deciding where to store unencrypted data, deciding to store encrypted sensitive data in shared preferences or in files depends on your specific use case. If you need to store some small amount of data in key value pairs, then EncryptedSharedPreferences is certainly the easier path to take. If you have a lot of data or complex data, go with encrypted files.

Using EncryptedSharedPreferences

You use SharedPreferences on Android to persist configuration and preference data in an app. The data stores in the form of key-value pairs. JetSec provides an encrypted version of SharedPreferences, named EncryptedSharedPreferences, that provides strong security while maintaining performance for reading the key-value data. Keys and values can both be encrypted.

Key management: Creating and securing encryption keys

To use Jetpack Security, first, you need to add it to the app build dependencies.

security_version = "1.0.0"
implementation("androidx.security:security-crypto:$security_version")
private val sharedPreferences by lazy {
  // 1
	val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
  // 2
  val keyEncryptionScheme = EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV
  // 3
  val valueEncryptionScheme = EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
	// 4
  EncryptedSharedPreferences.create(
		ENCRYPTED_PREFS,
    masterKeyAlias,
    context,
    keyEncryptionScheme,
    valueEncryptionScheme
	)
}
fun getEncryptionKey(): String? {
	return sharedPreferences.getString(
  	ENCRYPTED_PREFS_ENCRYPTION_KEY,
    null
  )
}
if (current != getEncryptionKey()) {
  _snackbar.value = context.getString(R.string.error_current_encryption_key_incorrect)
  return
}
//1
if (new.isNullOrBlank()) {
  //2
  sharedPreferences.edit().putString(
    ENCRYPTED_PREFS_ENCRYPTION_KEY,
    null
  ).apply()
  _snackbar.value = context.getString(
    R.string.message_encryption_key_cleared
  )
} else {
  //3
  sharedPreferences.edit().putString(
    ENCRYPTED_PREFS_ENCRYPTION_KEY,
    new
  ).apply()
  _snackbar.value = context.getString(
    R.string.message_encryption_key_set
  )
}
The SimpleNote App Interface
Fte FuhxjuKoxe Uhw Imlowgeji

Encrypted files

For persisting and securing the primary app data with Jetpack Security, use encrypted files. Both files and shared preferences are abstractions that provide authenticated encryption with associated data, or AEAD. AEAD ensures both the confidentiality and integrity of data.

Reading an encrypted file

Open InternalFileRepository.kt and add the following function:

private fun getEncryptedEntry(name: String): EncryptedFile {
  // 1
  val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
  val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
  // 2
  val fileEncryptionScheme = EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
	// 3
  return EncryptedFile.Builder(
    // 4
    File(context.filesDir, name.urlEncode()),
    // 5
    context,
    masterKeyAlias,
    fileEncryptionScheme
    // 6
  ).build()
}
context.openFileOutput(note.fileName, Context.MODE_PRIVATE).use{ output ->
	output.write(text.toByteArray())
}
try {
  // 1
	val encryptedFile = getEncryptedEntry(note.fileName)
  encryptedFile.openFileOutput().use { output ->
  	output.write(text.toByteArray())
  }
} catch (e: Exception) {
  // 2
	e.printStackTrace()
  _snackbar.value = context.getString(R.string.error_unable_to_save_file)
}
override fun getNote(fileName: String): Note {
	val note = Note(fileName, "", 0)
	try {
		context.openFileInput(fileName).use { stream ->
    	val text = stream.bufferedReader().use {
        it.readText()
      }
    	note.dateModified = Date(noteFile(fileName).lastModified())
    	note.priority = text.takeLast(1).toInt()
    	note.noteText = text.dropLast(1)
    	return note
		}
	} catch (e:Exception) {
		e.printStackTrace()
    _snackbar.value = context.getString(R.string.error_unable_to_decrypt)
    return note
	}
}

Challenge

The sort order and priority filters still reset to their default values when you reload the app. They didn’t store in EncryptedSharedPreferences yet. As a challenge, update the app so that these values persist in EncryptedSharedPreferences.

Key points

  • Adding Jetpack Security to your project is quick and easy. The defaults work great out of the box.
  • Encrypted files use AES encryption to handle files of all sizes.
  • You use an encrypted file like a standard file with minor exceptions.
  • Keys for EncryptedSharedPreferences are encrypted deterministically.
  • EncryptedSharedPreferences implement SharedPreferences for interoperability.

Where to go from here?

The book Saving Data on Android is an in-depth exploration of the various ways to store data on a device. You can find more information about shared preferences, as well as the various storage scopes and domains on Android, here: https://www.raywenderlich.com/books/saving-data-on-android/

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now