Secure Android Applications - Pushing the Limits - Android Programming: Pushing the Limits (2014)

Android Programming: Pushing the Limits (2014)

Part III. Pushing the Limits

Chapter 12. Secure Android Applications

Security is such a complex topic that it deserves its own chapter in this book. Although security is composed of

many aspects, in this chapter I focus on how you as an application developer can manage the security around

your application and its data. Because of Andoid’s current, excellent API, I also include a section on device

administration.

Android Security Concepts

Android has an advanced security model for protecting application data and services from other applications.

Every application runs with its own unique user ID that provides basic protection. Each application is signed

with its unique key, which works as a foundation around the security model in the Android framework.

Moreover, Android’s permission system lets a given application share access to its component only to other

applications that explicitly declare the right permission in their manifest. Apps can also define permissions such

that only apps signed with the same key can use them. Finally, the Android APIs provide methods for verifying

signatures, for verifying the user ID of calling processes, and for using strong encryption schemes.

Signatures and Keys

All applications running in Android are signed with a key, including the Android system itself. During normal

development, you will use the auto-generated debug key for signing your application (which is done

automatically by the Gradle build system or the IDE). When you publish your application on the Google Play

Store, be sure to use a unique key that you generate manually using the keytool application.

You can use the same key for all the applications you publish, but I recommend that you create a unique

key for each application. The only reasons for using the same key for multiple applications is when the

applications need to access each other’s data directly, or you have defined permissions with the protection level

signature.

The following command shows how to generate a new, unique key for your application. A good practice is to

give the alias the same name as the package name for your application. Also, if you’re maintaining application

keys for multiple organizations, I suggest you use a separate keystore for each organization.

$ keytool -genkey -v -keystore <keystore filename> \

-alias <alias for key> -keyalg RSA -keysize 2048 -validity 10000

When you generate a new key, the keytool asks you for a password. If you don’t provide a password, anyone

with access to the keystore file can create a properly signed application. Thus it’s highly recommended that you

use a unique password for each keystore file.

Always back up your keystore files online—using, for example, Google Drive or Dropbox. Otherwise,

if you lose your keystore file or forget the password, you’ll no longer be able to sign new versions of

your application and that effectively makes it impossible to provide upgrades for your users.

Android Permissions

Using a feature in Android that requires a special permission is only a matter of adding a uses-permission

tag in the AndroidManifest.xml file. This tells the system that your application requires that particular

permission, and notifies the user about this requirement before installation.

Android defines five protection levels: normal, dangerous, signature, signatureOrSystem, and

system. The default level, unless otherwise specified, is normal, which is generally used to signal the system

that an application requires the functions for this permission. The user is notified before installation (usually in

the Google Play Store application) only when the permission is defined as dangerous.

A signature protection level requires that the application be signed with the same certificate as the

application that defined the permission, which can be useful for device vendors because they can define

permissions that only applications signed with the same certificate as the system can use. In this way, it’s

possible for device vendors to release new applications for their devices that use protected system services.

The signatureOrSystem and system protection levels tell the Android system that applications must

reside on the system partition of the device in order to be able to use the permission. The most common

examples of this feature are the Google applications (Gmail, Google Play Services, YouTube, and so on)

that come pre-installed on the system partition. These applications can use many of the permissions that

normal applications cannot reach, even though Google, and not the device manufacturer, signed them. The

signatureOrSystem is basically a combination of the two protection levels.

Declaring Custom Permissions

In most cases, your application will be self-contained, so there will be little need to declare any new permissions.

However, if you will provide an API for other applications to use, for instance a plug-in feature, I recommend

defining your own permissions.

The following example shows the relevant parts for defining a read and write permission on a

ContentProvider.

<?xml version=”1.0” encoding=”utf-8”?>

<manifest xmlns:android=”http://schemas.android.com/apk/res/android”

package=”com.aaptl.security”

android:versionCode=”1”

android:versionName=”1.0” >

<permission android:name=”com.aaptl.security.READ_DATA”

android:description=”@string/read_perm_desc”

android:label=”@string/read_perm_label”

android:protectionLevel=”dangerous” />

<permission android:name=”com.aptl.security.WRITE_DATA”

android:descriptiona=”@string/write_perm_desc”

android:label=”@string/write_perm_label”

android:protectionLevel=”signature” />

...

<provider

android:name=”.TaskProvider”

android:authorities=”com.aaptl.security.provider”

android:readPermission=”com.aaptl.security.READ_DATA”

android:writePermission=”com.aaptl.security.WRITE_DATA”

android:exported=”true”/>

...

</manifest>

The read permission’s protection level is set to dangerous, which will show up when the user wants to install

an application that uses this permission. The write permission, on the other hand, has its protection level set to

signature, which limits the user of this permission to applications signed with the same certificate.

You can also add the attribute android:permissionFlags=”costsMoney” , which signals

the user that an application using this permission will generate a cost. Common examples are

applications that want to send SMS. Whenever your application provides an API that could result in a

monetary cost for users, protect the API with a permission that has this flag set.

Protecting User Data

If your application creates content that shouldn’t be accessible to other applications, store it in the default data

directory for the application. Storing data on external storage is never safe, unless you encrypt the content (see

the section “Client-Side Data Encryption,” later in this chapter). Files stored in an application’s data directory can

be accessed only by that application or an application with the same user ID (which requires the applications to

be signed by the same certificate).

When you create databases using the SQLiteOpenHelper, as shown in Chapter 9, they end up in the

application’s data directory by default, so these SQLite databases are always protected from other applications.

To create ordinary files, however, you use the method Context.openFileOutput(), which creates a new

file (or opens an existing file for appending data) in the application’s data directory.

The following method is an example of how to append data to a private file.

public static void appendStringToPrivateFile(Context context,

String data, String filename)

{

FileOutputStream outputStream

= context.openFileOutput(filename,

Context.MODE_APPEND|Context.MODE_PRIVATE);

outputStream.write(data.getBytes(“UTF-8”));

outputStream.close();

}

You open files for reading using Context.openFileInput(). Notice the use of both Context.MODE_

APPEND and Context.MODE_PRIVATE as flags to openFileOutput(). These flags will make the file

always append data to the end when writing and accessible only to your application, respectively. To protect

your data, always use MODE_PRIVATE (which is also the default flag). To provide a file that other applications

can read, you should instead use a ContentProvider and define the appropriate permissions.

The two flags Context.MODE_WORLD_READABLE and Context.MODE_WORLD_WRITABLE make

files in the application’s data directory world readable and writable. However, these flags are now

deprecated, and you’re strongly encouraged not to use them.

Because the MODE_PRIVATE flag sets the correct Linux file permissions, they protect files from being accessed

by other applications. However, simply using the method described in the preceding example should not be

considered a secure solution. If you’re storing very sensitive information on a device, I strongly advise applying

some encryption to the files.

Verifying Calling Applications

Although permissions provide a way of ensuring that the user accepts that an application can access your

services or providers, at times it’s necessary to identify the application of an incoming call. Consider a

ContentProvider that provides a way for third-party applications to add (insert) data. However, you want

applications to be capable of accessing only the records they’ve’ created, so you need some way of securely

identifying the remote process in each call to query(), insert(), update(), and delete().

The following method shows how to use the Binder to retrieve the UID (User ID) for the calling applications.

From there, you can retrieve the package names (that is, applications) that have this UID.

private String getPackageNameForCaller(Context context) {

int callingUid = Binder.getCallingUid();

PackageManager packageManager = context.getPackageManager();

String[] packages = packageManager.getPackagesForUid(callingUid);

if(packages != null && packages.length > 0) {

return packages[0]; // Return the first matching package...

}

return null;

}

Because you can have the same UID for multiple applications, signed with the same key, by using the

android:sharedUserId attribute in the manifest, you may receive multiple package names in this lookup.

So, in the public documentation of your provider, you need to state that applications using your API can’t share

the UID with another application.

After you get the package name for the application, you can use it when querying the database. For each insert,

make sure that you populate a column with the value of the calling application’s package name. In the query,

update and delete, you modify the selection and selection argument so that the package name is prepended,

as shown here:

private String prependSelection(String selection) {

return Columns.PACKAGE_NAME + “ = ? AND (“ + selection + “)”;

}

private String[] prependSelectionArgs(String[] selectionArgs) {

String[] newSelArgs = new String[selectionArgs.length + 1];

System.arraycopy(selectionArgs, 0, newSelArgs, 1, selectionArgs.

length);

newSelArgs[0] = getPackageNameForCaller(getContext());

return newSelArgs;

}

In this way, you can provide a single ContentProvider that other applications can work with while

simultaneously providing protection so that data cannot leak. This approach is suitable for applications

that aggregate data from different sources and in which you enable the use of plug-ins from third-party

developers.

Client-Side Data Encryption

Imagine an app that syncs user-generated data to a cloud service. However, in this case, the data stored on the

cloud service must be encrypted so that no one but the user can decrypt the data. This scenario means that

the encryption and decryption must happen on the client-side. A couple of examples today are LastPass, which

manages all your passwords, and Wuala, a cloud-storage service where all data is encrypted on the client-side,

which effectively protects the user’s data even if the cloud service is compromised.

The challenge with such an application is that you wouldn’t want to store anything on the device that isn’t

encrypted. For instance, if you have a notebook application for storing secure notes, you can’t store decrypted

notes in the application’s data directory, because if the user were to lose the device, it wouldn’t take much effort

for a hacker to gain access to the file system and read the unencrypted files. To solve this problem, only keep

unencrypted data in memory. Although it’s still possible to read the state of a memory, doing so is significantly

harder than reading the file system.

In this section, I explain how to encrypt and decrypt data using a password and how to create a memory-based

database that you can use in your application like any other ContentProvider.

Android Crypto API

The API for encrypting and decrypting data in Android is based on the API found in the javax.crypto

package in Java SE. The actual implementation is based on the open-source Bouncy Castle Crypto APIs, a clean

room implementation of the JCE 1.2.1 specification. As a result, you can use most Java libraries that use the

javax.crypto APIs from Java SE when writing Android applications.

Generating a Key

When using encryption and decryption functions, you need to generate a key that is secure, unique, and

capable of being re-created based on the user’s input (that is, a password or other secure method).

The following code shows how to generate a SecretKey for the AES algorithm, which is secure enough

for most situations. The salt is the input to the generation of the key that you need to keep track of. In

cryptography, a salt is a random piece of data used as input for the one-way function in the encryption

algorithms.

public static SecretKey generateKey(char[] password, byte[] salt)

throws Exception {

int iterations = 1000;

int outputKeyLength = 256;

SecretKeyFactory secretKeyFactory

= SecretKeyFactory.getInstance(“PBKDF2WithHmacS

HA1”);

KeySpec keySpec = new PBEKeySpec(password, salt,

iterations, outputKeyLength);

byte[] keyBytes = secretKeyFactory.generateSecret(keySpec).

getEncoded();

return new SecretKeySpec(keyBytes, “AES”);

}

Encrypting Data

To encrypt data, you must generate the salt and an initialization vector as an input to the Cipher used for

encrypting.

The following method generates a salt of 8 bytes using the SecureRandom class. Note: You never manually

seed the SecureRandom class because the system takes care of that for you. You create an initialization

vector, initiate the Cipher, and encrypt your plain text to a byte array. When you have your cipher data, you

generate a normal String from those bytes by using the Base64 utility class. You append the initialization

vector and salt in the same way, separated by a non-base64 character.

public static String encryptClearText(char[] password, String plainText)

throws Exception {

SecureRandom secureRandom = new SecureRandom();

int saltLength = 8;

byte[] salt = new byte[saltLength];

secureRandom.nextBytes(salt);

SecretKey secretKey = generateKey(password, salt);

Cipher cipher = Cipher.getInstance(“AES/CBC/PKCS5Padding”);

byte[] initVector = new byte[cipher.getBlockSize()];

secureRandom.nextBytes(initVector);

IvParameterSpec ivParameterSpec = new IvParameterSpec(initVector);

cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);

byte[] cipherData = cipher.doFinal(plainText.getBytes(“UTF-8”));

return Base64.encodeToString(cipherData,

Base64.NO_WRAP | Base64.NO_PADDING)

+ “]” + Base64.encodeToString(initVector,

Base64.NO_WRAP | Base64.NO_PADDING)

+ “]” + Base64.encodeToString(salt,

Base64.NO_WRAP | Base64.NO_PADDING);

}

The returned String can be securely sent over the network or stored on the external storage. An application

using this approach for securing data should require the user to input a password that is secure enough. How

to choose such a password is beyond the scope of this book, but I recommend using a regular expression

for checking the complexity of a password. A good source for various regular expressions that you can use is

available at http://regexlib.com/.

Decrypting Data

Decrypting data works very much like encryption. You take the String generated by a previous call to

encryptClearText() and split it on the separator that you choose.

In the following method, you see how the Cipher, the initialization vector, and the SecretKey are re-created

from the input string. As long as the passwords match, you’ll be able to decode the encoded data.

public static String decryptData(char[] password, String encodedData)

throws Exception {

String[] parts = encodedData.split(“]”);

byte[] cipherData = Base64.decode(parts[0], Base64.DEFAULT);

byte[] initVector = Base64.decode(parts[1], Base64.DEFAULT);

byte[] salt = Base64.decode(parts[2], Base64.DEFAULT);

Cipher cipher = Cipher.getInstance(“AES/CBC/PKCS5Padding”);

IvParameterSpec ivParams = new IvParameterSpec(initVector);

SecretKey secretKey = generateKey(password, salt);

cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParams);

return new String(cipher.doFinal(cipherData), “UTF-8”);

}

This method is useful for fairly small sets of data. If you need to encrypt and decrypt larger files, check the

CipherInputStream and CipherOutputStream as they support streaming data that is too large to fit in

memory all at once.

Working with Encrypted Data

After you encode the data, you can safely write it to a file or send it over the network without the risk of leaking the

user’s private information. The only one who can decrypt the data is the person who knows the password, which

means that you could basically store the encrypted data on a public server without anyone, including yourself,

being able to read its content. This is a great way to gain your users’ trust—as long as you can explain it in terms

that nontechnical people can grasp. A couple of services already use this capability as their main selling point.

Considering the example of a secure notebook application, you could use the approach just described for

encrypting and decrypting the notes. When the user makes an update to his notes, you can upload the file to

his Google Drive or similar service, and the notes will then be accessible from his other devices. (See Chapter 19

for an example of storing data in Google Drive.)

Consider the following Java object for representing notes in a notebook application:

public class NoteData {

private UUID mID;

private String mTitle;

private String mContent;

private String mCategory;

private Date mLastChange;

public NoteData() {

mID = UUID.randomUUID();

}

// Getters and setters omitted for brevity...

}

You can use Google Gson, as shown in Chapter 9, to serialize and deserialize objects of this class to the JSON

format. The JSON data can be represented as a normal String object, which you then can use for encrypting

your notes. Decrypting and deserializing works the same way.

The following example shows a simplified method for use in a notebook application that securely encrypts and

decrypts the users’ notes based on their passwords. The encrypted data can then be transferred online, which I

cover in more detail in Chapter 17.

public String encryptNoteDataCollection(Collection<NoteData> notes,

char[] password) {

StringWriter writer = new StringWriter();

JsonWriter jsonWriter = new JsonWriter(writer);

Gson gson = new Gson();

Type type = new TypeToken<Collection<NoteData>>(){}.getType();

gson.toJson(notes, type, jsonWriter);

String clearText = writer.toString();

try {

return encryptClearText(password, clearText);

} catch (Exception e) {

// Ignore for brevity

return null;

}

}

public static Collection<NoteData> decryptAndDecode(char[] password,

String encryptedData)

{

try {

String jsonData = decryptData(password, encryptedData);

Gson gson = new Gson();

Type type = new TypeToken<Collection<NoteData>>(){}.getType();

JsonReader jsonReader = new JsonReader(new

StringReader(jsonData));

return gson.fromJson(jsonReader, type);

} catch (Exception e) {

// Ignore for brevity...

return null;

}

}

If you prefer to work toward a ContentProvider, you can create an SQLite database in memory using the

following code:

mDatabase = SQLiteDatabase.create(null);

This database instance will be destroyed as soon as the database object is closed. This means that your

application should store the encrypted content to a local file for every change (insert, update or delete) to the

database and must repopulate the database content each time it is opened.

Android Key Chain Management

A feature that was long missing from Android was central secure credential storage and a way to control the

system’s trusted Certificate Authorities, which are crucial in order to build secure applications for enterprise and

other sensitive services.

In this section I’m assuming that you’re familiar with the basics of certificates, CA and PKI. If not, refer to the

“Further Resources” section at the end of this chapter. Although permissions can protect the data from other

applications and encryption can prevent access of the data even when the file is copied from the device, your

application still needs a secure method for verifying that the remote host it communicates with is indeed the

one it claims to be. Because you cannot assume that the user will always be on a safe, well-known, and secure

network, you need to verify any host that you communicate with using its certificates.

Explaining the details of certificate authority, key chains, and how they make the Internet secure is beyond the

scope of this book. However, I do show how you can use the key chain API that was made available in Android

4.0/ICS to store a new key pair, as well as how to verify the source of the data and sign it so that a recipient can

verify its authenticity.

First, you need to generate a new certificate that you can use for testing your application, which you do using

the same keytool shown earlier in this chapter for creating the signature for your application.

The following example shows how to generate a simple certificate using the keytool command. The result is

written to a file named MyKeyStore.pfx. In a real-world situation, the IT department of a company usually

generates these certificates for each user and distributes them securely. Next, the enterprise application imports

this certificate and stores it in the central credential storage.

$ keytool -genkeypair -alias MyCertificate -keystore MyKeyStore.pfx

-storepass thepassword -validity 10000 -keyalg RSA -keysize 2048

-storetype pkcs12

What is your first and last name?

[Unknown]: Erik Hellman

What is the name of your organizational unit?

[Unknown]: Development

What is the name of your organization?

[Unknown]: Hellsoft

What is the name of your City or Locality?

[Unknown]: Malmoe

What is the name of your State or Province?

[Unknown]: Skaane

What is the two-letter country code for this unit?

[Unknown]: SE

Is CN=Erik Hellman, OU=Development, O=Hellsoft, L=Malmoe, ST=Skaane,

C=SE correct?

[no]: yes

In the following code, you see how to install a certificate from a file (represented as a byte array).

public class SigningActivity extends Activity {

private static final int INSTALL_CERT_CODE = 1001;

private static final String CERT_FILENAME = “MyKeyStore.pfx”;

private static final String CERTIFICATE_NAME = “MyCertificate”;

@Override

protected void onActivityResult(int requestCode, int resultCode,

Intent data) {

if(requestCode == INSTALL_CERT_CODE) {

if(resultCode == RESULT_OK) {

// Certificate successfully installed

} else {

// User cancelled certificate installation

}

}

}

// click-listener for installing certificate

public void doInstallCertificate(View view) {

byte[] certData = readFile(CERT_FILENAME);

Intent installCert = KeyChain.createInstallIntent();

installCert.putExtra(KeyChain.EXTRA_NAME, CERTIFICATE_NAME)

installCert.putExtra(KeyChain.EXTRA_PKCS12, certificateData);

startActivityForResult(installCert, INSTALL_CERT_CODE);

}

...

}

image

The code will activate a dialog box where a user can enter the password for the chosen keystore. Next, another

dialog box, as shown in Figure 12-1, is activated in which the user can choose the alias for the certificate.

Figure 12-1 Dialog box asking for the name of the

certificate that is being installed by the application

When your application wants to use a certificate, it must first ask users which one they want to use, which is

done by calling KeyChain.choosePrivateKeyAlias(), as shown in the following code.

public void doSignNoteData(View view) {

KeyChain.choosePrivateKeyAlias(this, new KeyChainAliasCallback() {

@Override

public void alias(String alias) {

EditText editText = (EditText) findViewById(R.id.input_text);

String textToSign = editText.getText().toString();

new MySigningTask().execute(textToSign, alias);

}

}, null, null, null, -1, null);

}

image

This activates a system dialog box, as shown in Figure 12-2.

Figure 12-2 System dialog box that allows the user to

pick a certificate from the trusted storage

The preceding callback will receive the alias for the chosen certificate, and you can now continue with retrieving

the certificate and the signing process. Note: The callback is executed on a Binder thread and not on the main

thread. Because the signing process could potentially block the thread it’s running on, you use an AsyncTask

for the actual signing.

The following method shows how to retrieve the private key of the certificate pair and use it to create a

signature of the String you want to sign. You use the same approach as in the earlier encryption example to

build two Base64 encoded Strings with a separator.

public String createSignedNote(String textToSign, String alias) {

try {

byte[] textData = textToSign.getBytes(“UTF-8”);

PrivateKey privateKey

= KeyChain.getPrivateKey(getApplicationContext(), alias);

Signature signature

= Signature.getInstance(“SHA1withRSA”);

signature.initSign(privateKey);

signature.update(textData);

byte[] signed = signature.sign();

return Base64.encodeToString(textData,

Base64.NO_WRAP | Base64.NO_PADDING)

+ “]” + Base64.encodeToString(signed,

Base64.NO_WRAP | Base64.NO_PADDING);

} catch (Exception e) {

Log.e(TAG, “Error signing data.”, e);

}

return null;

}

To verify the signature of signed data, your application must first retrieve the PublicKey from the certificate.

In the following code, it’s assumed that the chain contains only one entry, but in real-life situations, you will

probably have at least two entries (one for the organization and one for the user).

private boolean verifySignature(String dataAndSignature, String alias) {

try {

String[] parts = dataAndSignature.split(“]”);

byte[] decodedText = Base64.decode(parts[0], Base64.DEFAULT);

byte[] signed = Base64.decode(parts[1], Base64.DEFAULT);

X509Certificate[] chain = KeyChain.getCertificateChain(this,

alias);

PublicKey publicKey = chain[0].getPublicKey();

Signature signature = Signature.getInstance(“SHA1withRSA”);

signature.initVerify(publicKey);

signature.update(decodedText);

return signature.verify(signed);

} catch (Exception e) {

Log.e(TAG, “Error verifying signature.”, e);

}

return false;

}

You can also use the public and private keys to encrypt data. If two parties (for instance, the company server

and an application on an employee’s smartphone) need to communicate securely, you use the receiving party’s

public key to encrypt data, which then can be decrypted only by using the private key.

Most webservers today support the secure HTTP protocol called HTTPS. This protocol has built-in support to

set up the connection between the client and the server using this approach, so all you do is provide the

client-side certificate. Refer to the documentation for the HttpsURLConnection for more information on

this topic.

Device Management API

Although encrypting and verifying data are important parts of security, they’re often not enough for

most organizations. Within the domain of enterprise applications is a need to set certain restrictions on a

device before allowing it to communicate with a company’s intranet. This situation is resolved via a Device

Management API available in Android that provides a number of functions to handle enterprise requirements.

This API provides a number of policies that an application can apply to a device to ensure that it is secure. There

are policies for limiting and resetting the password, for locking and wiping a device, and for encrypting the

storage. Check the documentation for the Device Administration API (see http://developer.android.

com/guide/topics/admin/device-admin.html) for a full list of policies.

The first thing you need to enable this in your application is an XML file in the resources that defines which

device policies your application needs to apply.

The following example shows a simple device policy XML that you store in the XML resources. It enables your

application to register when a user logs in (unlocks the device), to limit and reset the password, to force the

device to lock down, and to encrypt the storage when required.

<?xml version=”1.0” encoding=”utf-8”?>

<device-admin xmlns:android=”http://schemas.android.com/apk/res/android”>

<uses-policies>

<watch-login/>

<force-lock/>

<limit-password/>

<encrypted-storage/>

<reset-password/>

</uses-policies>

</device-admin>

Next, you need to define a BroadcastReceiver that will be called when the device administration is

enabled for your application.

<receiver

android:name=”.MyDeviceAdminReceiver”

android:label=”@string/device_admin_label”

android:description=”@string/device_admin_description”

android:permission=”android.permission.BIND_DEVICE_ADMIN”>

<meta-data

android:name=”android.app.device_admin”

android:resource=”@xml/device_admin”/>

<intent-filter>

<action android:name=”android.app.action.DEVICE_ADMIN_ENABLED”/>

</intent-filter>

</receiver>

Now you need to implement a custom BroadcastReceiver (extending DeviceAdminReceiver) where

you configure the manifest entry, as in the preceding example. First, you specify that the permission for the

receiver and add a meta-data tag that specifies the XML also shown in the preceding example.

public class MyDeviceAdminReceiver extends DeviceAdminReceiver {

@Override

public void onEnabled(Context context, Intent intent) {

super.onEnabled(context, intent);

PackageManager packageManager = context.getPackageManager();

ComponentName deviceAdminService

= new ComponentName(context, MyDeviceAdminService.class);

packageManager.setComponentEnabledSetting(deviceAdminService,

PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);

context.startService(new Intent(context,

MyDeviceAdminService.class));

}

@Override

public void onDisabled(Context context, Intent intent) {

super.onDisabled(context, intent);

context.stopService(new Intent(context,

MyDeviceAdminService.class));

PackageManager packageManager = context.getPackageManager();

ComponentName deviceAdminService

= new ComponentName(context, MyDeviceAdminService.class);

packageManager.setComponentEnabledSetting(deviceAdminService,

PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);

}

}

The receiver is notified when device administration for your application is enabled or disabled. There are also

callbacks for certain device policy events, such as password expiration. In the preceding example, you see how

to enable and disable a service in your application, depending on the state of device administration for this

application.

To enable device administration for your application, you need to start a system Activity that asks for the

user’s permission (see Figure 12-3).

image

Figure 12-3 The system Activity that asks for permission

to enable device administration rights for an application .

The first paragraph under the title is the same as the text in

the EXTRA_ADD_EXPLANATION parameter .

The following example shows how to request the user to enable device administration for your application.

Note: You send the ComponentName for the receiver and you can add an extra explanation that is displayed

to the user. This is recommended for applications using the Device Administration API; otherwise, what the

prompted dialog box means may be confusing.

public static void enableDeviceAdmin(Context context) {

Intent enableDeviceAdmin

= new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);

ComponentName deviceAdminReceiver

= new ComponentName(context, MyDeviceAdminReceiver.class);

enableDeviceAdmin.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN,

deviceAdminReceiver);

enableDeviceAdmin.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,

context.getString(R.string.device_admin_explanation));

context.startActivity(enableDeviceAdmin);

}

After the user accepts the device administration request, your application can start applying its policies and

control the device to a certain degree.

The following method is a simple example in which you reset the password and lock the device immediately.

private void changePasswordAndLockDevice(String password) {

mDevicePolicyManager = (DevicePolicyManager)

getSystemService(DEVICE_POLICY_SERVICE);

if(mDevicePolicyManager.resetPassword(password, 0)) {

mDevicePolicyManager.lockNow();

}

}

Doing so can be useful for a remote security feature. By sending a verified message (as shown earlier in this

chapter) to the device, the user or someone with the right authorization can lock the device if it’s stolen or

lost. The message could come from an SMS, which I describe in Chapter 15, or from a Google Cloud Messaging

(GCM) service, which I cover in Chapter 19.

Summary

In this chapter, I described how you can use the Android permissions to restrict what and how other

applications can access your application. I also showed how to identify the calling application in order to isolate

data you receive from multiple applications. This feature is especially powerful when your application is working

with aggregated data from several sources.

I then demonstrated how to encrypt and decrypt data using the crypto APIs in Android, which can often

provide enough security when the user’s data for your application is stored externally or on a remote server.

If your application will involve external parties, such as a remote server, simply performing encryption of

the data may not be enough. You also need to be able to verify that data your application receives is coming

from a verified source. The new Key Chain API in Android 4.0 and later provides secure and trusted storage for

application-specific certificates.

Finally, I showed how to use the Device Administration API in order to programmatically control the security

features. As a result, you can write applications that can lock a device or reset the password. The Device

Administration API combined with the other security features described in this chapter give you a set of

powerful tools for creating applications that fulfill the security requirements of most enterprises.

Further Resources Books

Adams, Carlisle, and Steve Lloyd. Understanding PKI: Concepts, Standards, and Deployment Considerations,

2nd edition. Addison-Wesley, 2002.

Documentation

Documentation on the Device Administration APIs: http://developer.android.com/guide/topics/admin/device-admin.html

Websites

Android Developers Blog. “Using Cryptography to Store Credentials Safely” : http://android-developers.blogspot.se/2013/02/using-cryptography-to-store-credentials.html

Android Developers Blog. “Unifying Key Store Access in ICS”: http://android-developers.blogspot.se/2012/03/unifying-key-store-access-in-ics.html

Android Explorations. “Using the ICS KeyChain API”: http://nelenkov.blogspot.se/2011/11/using-ics-keychain-api.html

“Sample CodeC for Using the ICS KeyChain API and Related Articles”: https://github.com/nelenkov/keystore-test#readme