- How to Secure an Android App
- Introduction
- 1. Use Internal Storage for Sensitive Data
- 2. Encrypt Data on External Storage
- 3. Use Intents for IPC
- 4. Use HTTPS
- 5. Use GCM Instead of SMS
- 6. Avoid Asking for Personal Data
- 7. Validate User Input
- 8. Use ProGuard Before Publishing
- Conclusion
- Storing Data Securely on Android
- The Basics
- Securing User Data With a Password
- AES and Password-Based Key Derivation
- Initialization Vectors
- Encrypting the Data
- The Decryption Method
- Testing the Encryption and Decryption
- Saving Encrypted Data
- Saving Secure Data to SharedPreferences
- Wiping Insecure Data From Old Versions
- Conclusion
How to Secure an Android App
Introduction
The Android operating system has lots of built-in security features, such as application sandboxing, protection against buffer and integer overflow attacks, and segregated memory areas for program instructions and data. As a result, simple Android apps that don’t perform any file system or networking operations can often be considered secure by default.
If you are developing a more complex app, however, it is your responsibility to make it secure and protect the privacy of your users. In this article, I’m going to list some of the best practices you can follow to build a secure Android app that doesn’t leak data or permissions, and is, in general, less vulnerable to malicious apps that might be installed on the user’s device.
1. Use Internal Storage for Sensitive Data
Every Android app has an internal storage directory associated with it whose path is based on the package name of the app. Files inside this directory are very secure because they use the MODE_PRIVATE file creation mode by default. This means the files cannot be accessed by any other app on the device. Therefore, it is a best place to store all the sensitive data of your app in the internal storage directory.
To determine the absolute path of your app’s internal storage directory, it is recommended that you use the getFilesDir() method. Once you know its path, referencing files inside it is as simple as referencing files inside any other directory. For example, here’s how you could reference a file called myfile.dat in the internal storage directory of your app:
2. Encrypt Data on External Storage
The internal storage capacity of an Android device is often limited. Therefore, at times, you might have no choice but to store sensitive data on external storage media, such as a removable SD card.
Because data on external storage media can be directly accessed by both users and other apps on the device, it is important that you store it in an encrypted format. One of the most popular encryption algorithms used by developers today is AES, short for Advanced Encryption Standard, with a key size of 256 bits.
Writing code to encrypt and decrypt your app’s data using the javax.crypto package, which is included in the Android SDK, can be confusing. Therefore, most developers prefer using third party libraries, such as Facebook’s Conceal library, which are usually much easier to work with.
3. Use Intents for IPC
Experienced programmers who are new to Android application development often try to use sockets, named pipes, or shared files to asynchronously communicate with other apps installed on an Android device. These approaches are not only hard and inelegant, but also prone to threats. An easier and more secure approach to interprocess communication on the Android operating system is to use intents.
To send data to a specific component of an app, you must create a new instance of the Intent class and use its setComponent() method to specify both the package name of the app and the name of the component. You can then add data to it using the putExtra() method.
For example, here’s how you could send the string Hello World to an Activity called MyActivity, which belongs to an app whose package name is my.other.app:
To send data to multiple apps at once, you can send the intent as a broadcast using the sendBroadcast() method. However, by default, a broadcast can be read by any app that has an appropriately configured BroadcastReceiver .
Therefore, if you want to send sensitive information as a broadcast, you must use a custom permission whose protectionLevel is set to signature . By doing so, the Android operating system makes sure that only apps that were signed using your signing key can receive the broadcast.
Here’s a code snippet that shows you how to send the string Hello World as a secure broadcast:
Note that the above code works as expected only if the custom permission is declared and used in the manifest files of both the sender and receiver apps.
4. Use HTTPS
All communications between your app and your servers must be over an HTTPS connection, preferably using the HttpsURLConnection class. If you think using HTTP for data that is not confidential is fine, think again.
Many Android users connect to several open Wi-Fi hotspots in public areas every day. Some of those hotspots could be malicious. A malicious hotspot can easily alter the contents of HTTP traffic to make your app behave in an unexpected manner, or worse still, inject ads or exploits into it.
By using HTTPS, as long as the server is configured with a certificate issued by a trusted certificate authority, such as DigiCert or GlobalSign, you can be sure that your network traffic is secure against both eavesdropping and man-in-the-middle attacks.
If your app has a lot of networking code and you are afraid that you might unwittingly be sending some data as cleartext, you should consider using nogotofail, an open source tool built by Google to find such mistakes.
5. Use GCM Instead of SMS
Back when GCM, short for Google Cloud Messaging, didn’t exist, many developers were using SMS to push data from their servers to their apps. Today, this practice is largely gone.
If you are one of those developers who still hasn’t made the switch from SMS to GCM, you must know that the SMS protocol is neither encrypted nor safe against spoofing attacks. What’s more, an SMS can be read by any app on the user’s device that has the READ_SMS permission.
GCM is a lot more secure and is the preferred way to push messages to an app because all GCM communications are encrypted. They are authenticated using regularly refreshed registration tokens on the client side and a unique API key on the server side. To learn more about GCM, you can refer to this tutorial about push notifications .
6. Avoid Asking for Personal Data
User privacy is given a lot of importance these days. In fact, there are laws, such as the European Union’s Data Protection Directive and Canada’s Personal Information Protection and Electronic Documents Act, which mandate the protection of the privacy of a user. Therefore, unless you have a good reason and a very secure infrastructure to collect, store, and transmit personal user information, you must avoid directly asking for it in your apps.
A better approach to user authentication and user profile information look up on Android is through the Google Identity Platform . Google Identity Platform allows users to quickly sign in to your app using their Google account. After a successful sign in through the platform, whenever necessary, your app can easily look up various details about the user, such as the user’s name, email address, profile photo, contacts, and more. Alternatively, you could use free services like Firebase that can manage user authentication for you.
If you must handle user credentials yourself, it is recommended that you store and transmit them in the form of secure hashes. The most straightforward way to generate different types of hashes using the Android SDK is by using the MessageDigest class.
Here’s a little code snippet that shows you how to create a hash of the string Hello World using the SHA-256 hashing function:
7. Validate User Input
On Android, invalid user input doesn’t usually lead to security issues like buffer overruns. However, if you allow users to interact with a SQLite database or a content provider that internally uses a SQLite database, you must either rigorously sanitize user input or make use of parameterized queries. Failing to do so makes your data vulnerable to SQL injection attacks.
On a similar note, user input validation and sanitization is also very important if you are using user input to dynamically generate code to run on an embedded scripting engine, such as Mozilla Rhino .
8. Use ProGuard Before Publishing
Security measures built into an Android app can be severely compromised if attackers are able to get their hands on the source code. Before you publish your app, it is recommended to make use of a tool called ProGuard, which is included in the Android SDK, to obfuscate and minify source code.
Android Studio automatically includes ProGuard in the build process if the buildType is set to release . The default ProGuard configuration available in the Android SDK’s proguard-android.txt file is sufficient for most apps. If you want to add custom rules to the configuration, you can do so inside a file named proguard-rules.pro, which is a part of every Android Studio project.
Conclusion
I hope you now have a better understanding of how to make your Android apps secure. Most of the best practices I mentioned in this article are applicable only if you are using the Android SDK to develop your apps. If you are using the Android NDK instead, you have to be a lot more careful because, while programming in the C language, you are expected to manage low-level details, such as pointers and memory allocation yourself.
To learn more about security on Android, you can refer to the AOSP security documents.
Источник
Storing Data Securely on Android
An app’s credibility today highly depends on how the user’s private data is managed. The Android stack has many powerful APIs surrounding credential and key storage, with specific features only available in certain versions.
This short series will start off with a simple approach to get up and running by looking at the storage system and how to encrypt and store sensitive data via a user-supplied passcode. In the second tutorial, we will look at more complex ways of protecting keys and credentials.
The Basics
The first question to think about is how much data you actually need to acquire. A good approach is to avoid storing private data if you don’t really have to.
For data that you must store, the Android architecture is ready to help. Since 6.0 Marshmallow, full-disk encryption is enabled by default, for devices with the capability. Files and SharedPreferences that are saved by the app are automatically set with the MODE_PRIVATE constant. This means the data can be accessed only by your own app.
It’s a good idea to stick to this default. You can set it explicitly when saving a shared preference.
Or when saving a file.
Avoid storing data on external storage, as the data is then visible by other apps and users. In fact, to make it harder for people to copy your app binary and data, you can prevent users from being able to install the app on external storage. Adding android:installLocation with a value of internalOnly to the manifest file will accomplish that.
You can also prevent the app and its data from being backed up. This also prevents the contents of an app’s private data directory from being downloaded using adb backup . To do so, set the android:allowBackup attribute to false in the manifest file. By default, this attribute is set to true .
These are best practices, but they won’t work for a compromised or rooted device, and disk encryption is only useful when the device is secured with a lock screen. This is where having an app-side password that protects its data with encryption is beneficial.
Securing User Data With a Password
Conceal is a great choice for an encryption library because it gets you up and running very quickly without having to worry about the underlying details. However, an exploit targeted for a popular framework will simultaneously affect all of the apps that rely on it.
It’s also important to be knowledgeable about how encryption systems work in order to be able to tell if you’re using a particular framework securely. So, for this post, we are going to get our hands dirty by looking at the cryptography provider directly.
AES and Password-Based Key Derivation
We will use the recommended AES standard, which encrypts data given a key. The same key used to encrypt the data is used to decrypt the data, which is called symmetric encryption. There are different key sizes, and AES256 (256 bits) is the preferred length for use with sensitive data.
While the user experience of your app should force a user to use a strong passcode, there is a chance that the same passcode will also be chosen by another user. Putting the security of our encrypted data in the hands of the user is not safe. Our data needs to be secured instead with a key that is random and large enough (i.e. that has enough entropy) to be considered strong. This is why it’s never recommended to use a password directly to encrypt data—that is where a function called Password-Based Key Derivation Function (PBKDF2) comes into play.
PBKDF2 derives a key from a password by hashing it many times over with a salt. This is called key stretching. The salt is just a random sequence of data and makes the derived key unique even if the same password was used by someone else.
Let’s start by generating that salt.
The SecureRandom class guarantees that the generated output will be hard to predict—it is a «cryptographically strong random number generator». We can now put the salt and password into a password-based encryption object: PBEKeySpec . The object’s constructor also takes an iteration count form, making the key stronger. This is because increasing the number of iterations expands the time it would take to operate on a set of keys during a brute force attack. The PBEKeySpec then gets passed into the SecretKeyFactory , which finally generates the key as a byte[] array. We will wrap that raw byte[] array into a SecretKeySpec object.
Note that the password is passed as a char[] array, and the PBEKeySpec class stores it as a char[] array as well. char[] arrays are usually used for encryption functions because while the String class is immutable, a char[] array containing sensitive information can be overwritten—thus removing the sensitive data entirely from the device’s memory.
Initialization Vectors
We are now ready to encrypt the data, but we have one more thing to do. There are different modes of encryption with AES, but we’ll be using the recommended one: cipher block chaining (CBC). This operates on our data one block at a time. The great thing about this mode is that each next unencrypted block of data is XOR’d with the previous encrypted block to make the encryption stronger. However, that means the first block is never as unique as all the others!
If a message to be encrypted were to start off the same as another message to be encrypted, the beginning encrypted output would be the same, and that would give an attacker a clue to figuring out what the message might be. The solution is to use an initialization vector (IV).
An IV is just a block of random bytes that will be XOR’d with the first block of user data. Since each block depends on all blocks processed up until that point, the entire message will be encrypted uniquely—identical messages encrypted with the same key will not produce identical results.
Let’s create an IV now.
A note about SecureRandom . On versions 4.3 and under, the Java Cryptography Architecture had a vulnerability due to improper initialization of the underlying pseudorandom number generator (PRNG). If you are targeting versions 4.3 and under, a fix is available.
Encrypting the Data
Armed with an IvParameterSpec , we can now do the actual encryption.
Here we pass in the string «AES/CBC/PKCS7Padding» . This specifies AES encryption with cypher block chaining. The last part of this string refers to PKCS7, which is an established standard for padding data that doesn’t fit perfectly into the block size. (Blocks are 128 bits, and padding is done before encryption.)
To complete our example, we will put this code in an encrypt method that will package the result into a HashMap containing the encrypted data, along with the salt and initialization vector necessary for decryption.
The Decryption Method
You only need to store the IV and salt with your data. While salts and IVs are considered public, make sure they are not sequentially incremented or reused. To decrypt the data, all we need to do is change the mode in the Cipher constructor from ENCRYPT_MODE to DECRYPT_MODE .
The decrypt method will take a HashMap that contains the same required information (encrypted data, salt and IV) and return a decrypted byte[] array, given the correct password. The decrypt method will regenerate the encryption key from the password. The key should never be stored!
Testing the Encryption and Decryption
To keep the example simple, we are omitting error checking that would make sure the HashMap contains the required key, value pairs. We can now test our methods to ensure that the data is decrypted correctly after encryption.
The methods use a byte[] array so that you can encrypt arbitrary data instead of only String objects.
Saving Encrypted Data
Now that we have an encrypted byte[] array, we can save it to storage.
If you didn’t want to save the IV and salt separately, HashMap is serializable with the ObjectInputStream and ObjectOutputStream classes.
Saving Secure Data to SharedPreferences
You can also save secure data to your app’s SharedPreferences .
Since the SharedPreferences is an XML system that accepts only specific primitives and objects as values, we need to convert our data into a compatible format such as a String object. Base64 allows us to convert the raw data into a String representation that contains only the characters allowed by the XML format. Encrypt both the key and the value so an attacker can’t figure out what a value might be for.
In the example above, encryptedKey and encryptedValue are both encrypted byte[] arrays returned from our encryptBytes() method. The IV and salt can be saved into the preferences file or as a separate file. To get back the encrypted bytes from the SharedPreferences , we can apply a Base64 decode on the stored String .
Wiping Insecure Data From Old Versions
Now that the stored data is secure, it may be the case that you have a previous version of the app that had the data stored insecurely. On an upgrade, the data could be wiped and re-encrypted. The following code wipes over a file using random data.
In theory, you can just delete your shared preferences by removing the /data/data/com.your.package.name/shared_prefs/your_prefs_name.xml and your_prefs_name.bak files and clearing the in-memory preferences with the following code:
However, instead of attempting to wipe the old data and hoping that it works, it’s better to encrypt it in the first place! This is especially true in general for solid state drives that often spread out data-writes to different regions to prevent wear. That means that even if you overwrite a file in the filesystem, the physical solid-state memory might preserve your data in its original location on disk.
Conclusion
That wraps up our tutorial on storing encrypted data. In this post, you learned how to securely encrypt and decrypt sensitive data with a user-supplied password. It’s easy to do when you know how, but it’s important to follow all the best practices to ensure your users’ data is truly secure.
In the next post, we will take a look at how to leverage the KeyStore and other credential-related APIs to store items safely. In the meantime, check out some of our other great articles on Android app development.
Источник