Skip to content

Developer Guide - DataVault

A secure solution for encrypted storage of arbitrary key-value pairs.

It is assumed that you are familiar with Android application development and Java programming in general.

Jump to the section of interest if you already know this guide:

  1. Introduction to DataVault
    1. Vault types
      1. Private
      2. Shared
    2. Lifecycle
      1. The vauld identifier
      2. The lifecycle API
    3. Data access
  2. Securing the DataVault
    1. Locking
    2. Password management
  3. UI flows

Introduction to DataVault

The DataVault library is a simple yet versatile solution for storing key-value pairs in a storage that is encrypted using a password. Its main purpose is to allow the application developer to store sensitive information such as credentials.

This storage is primarily intended to be used for relatively small amounts of data. It is not intended to be used for storing business data or OData entities of any form.

Under the hood, data vault uses SQLite databases and Android ContentProviders (depending on the type, see later) as the actual storage medium. The library has its own encryption layer on top of this medium, ensuring that proper encryption is performed when data is accessed.

It is recommended to read through this document in the order of the chapters to progressively understand how it works and what it can be used for.

Vault types

All kinds of data vaults inherit from the base com.sybase.persistence.DataVault class. This defines the public API used to interact with the encrypted storage underneath.

The various types of data vaults are represented by corresponding subclasses. What is common in these classes is that they all have identical lifecycle APIs in the form of static methods. Once the appropriate DataVault instance is at hand, the superclass API can be used to access its functionality.

Private

The private data vault is implemented by the com.sybase.persistence.PrivateDataVault class which makes use of an SQLite database to actually store data. This database is accessible only from the application which creates it.

This is the most commonly used type of data vault that is handy to keep certain information private to an application.

Shared

The shared data vault is implemented by com.sybase.persistence.SharedDataVault which uses the SMP Data Provider application (formerly known as Sybase Data Provider) as the storage medium. This application exposes an Android ContentProvider that the shared data vault talks to.

The shared data vault is an ideal choice if you'd like to share certain information between multiple applications. For example, if you intend to create a multi-app SSO mechanism, a shared data vault can be used to store backend credentials. As the user navigates between the apps, they can use the same password to unlock the shared vault and let the application access the credentials.

The user must install the SMP Data Provider application on the device prior to using the shared data vault-related functionality of your app. Attempting to use the SharedDataVault without it yields a DataVaultException. However, your app can catch this exception and display a dialog to the user to install the data provider application beforehand. Check the API documentations for the details.

Lifecycle

The vauld identifier

No matter the type of the data vault, a particular vault instance is always identified by the data vault identifier (in short: vault ID). This is an arbitrary string but it is recommended to use reverse-domain syntax, such as com.mycompany.myapp.mainvault.

The vault itself can be created, opened and deleted using its identifier.

Although the method of identification is independent of the vault type, the scope of the identifier does indeed depend on what kind of data vault we are talking about:

  • Private data vault identifiers are scoped to the current application.
  • Shared data vault identifiers are shared across applications, therefore the scope of the vault ID is the entire device. If you create a shared data vault in one app then you can open it with the same identifier in another one using the SharedDataVault class.

Of course, private and shared data vault identifiers may coexist. You can have a private and a shared vault for the same identifier, if you'd like. Nevertheless, this practice is not recommended as it might be more confusing in the long run.

The lifecycle API

It has been mentioned that the lifecycle API of the various types of data vaults are available as static methods on the corresponding data vault subclass. However, the signature and purpose of these methods is the same for all of them.

Unfortunately, Java lacks static interfaces therefore this cannot be expressed in a more concrete form. The API does not offer a factory pattern either due to backward compatibility reasons.

These are the methods that you can find on all data vault subclasses:

  • init(): Before interacting with a particular data vault class, it must be initialized with a suitable android.content.Context object. Call this method when your application starts before calling any of the other lifecycle methods.
  • createVault(): Use this method to create a new data vault using a vault ID and a password. You must ensure that a vault with the same identifier does not already exist.
  • getVault(): Opens an existing data vault for a particular vault ID. The vault must exist.
  • deleteVault(): Deletes a vault for the given identifier, provided that it exists. Does nothing if the vault has not been created yet.
  • vaultExists(): Indicates whether a data vault with a particular identifier exists or not.

You can find these methods on the PrivateDataVault and SharedDataVault classes as well. Their contracts are the same.

You can find these lifecycle methods on the DataVault abstract class too. This is due to historical reasons. They route calls to the same methods of SharedDataVault without change. In other words, you can create a shared data vault with the lifecycle methods of the base class too, if you like.

All the remaining operations are common to all kinds of data vaults. The methods mentioned in later chapters can all be found on the base DataVault class.

Data access

A data vault stores key-value pairs. A key is a string, a value can be either a string or a byte array.

The corresponding setters and getters are:

  • setString()/getString()
  • setValue()/getValue()

The data vault does not allow you to store null values. If you call setString() or setValue() with a null value then it deletes the previous association.

Deleting a value can also be done using the deleteValue() method as well. It's merely for convenience.

Note that a pair is uniquely addressed by solely the key. The type does not matter. Therefore, if you do the following...

DataVault dv = ...;
dv.setString("foo", "foo123");
dv.setValue("foo", "bar".getBytes(Charset.forName("UTF-8")));

...then the data vault will contain the UTF-8 raw byte representation of the string bar for the key foo insted of the previously set string value foo123.

If you'd like to get a list of keys stored in the vault then use the getDataNames() method which returns an array of com.sybase.persistence.DataVault.DVDataName objects. These contain the stored keys and the type of the data currently associated with the key.

Securing the DataVault

The data vault is protected by a password at all times, even though the password can be left empty. Protection means that the data vault can be in either a locked or an unlocked state.

In locked state, none of the data accessor methods are usable. If you try to read data from or write data to a data vault this time then you'll get a com.sybase.persistence.DataVaultException.

Securing the data vault properly is centered around creating the logic that locks and unlocks the data vault at the appropriate times. Typically, when the application is in the foreground and the user is interacting with it, you would want the data vault to be unlocked.

How to implement this logic and integrate it with your application is elaborated in a later chapter.

Locking

The current state of the data vault can be obtained simply by calling the isLocked() method. This tells whether the data vault is currently locked or not.

Locking the data vault can be done by calling the no-arg lock() method. This is idempotent and will not cause any harm if you lock the vault multiple times.

To unlock, you must use the unlock() method that requires that the password be specified as an argument.

Note that passwords are represented by byte arrays everywhere on the DataVault API, including the lifecycle static methods. This is because the passwords are zeroed out immediately after having been used. This ensures that the password spends the least amount of time in memory to protect against any potential attacks that might gather access to the memory contents of the app due to some exploit in the device or the app.

You'll find overloaded variants of both lifecycle and data vault methods which require passwords in java.lang.String form. These are deprecated and are kept only for backward compatibility. Always stick to the ones using byte[] arguments.

A speciality of the unlocking mechanism is that if incorrect passwords are tried too many times then the data vault self-destructs and wipes out all its contents. You can detect this by catching the DataVaultException that unlock() throws in this case and checking for the proper error code in the exception object.

Password management

Here, only the most important concepts of password management are covered. You can find the rest of the information in the Javadoc documentation.

How the password is managed and how it affects the behaviour of the data vault is determined by the Password policy (represented by an instance of the com.sybase.persistence.DataVault.DVPasswordPolicy class). The policy is used to describe the following rules that a particular data vault should adhere to:

  • The allowed set of characters in the password
  • Length constraints that the password must meet
  • Whether the use of the default password (i.e. the empty password) is allowed
  • How soon does the password expire (in days)
  • What's the longest time of inactivity after which an unlocked data vault is locked automatically

To set the password policy, use the setPasswordPolicy() setter method which is usable (with some limitations) even in locked state.

You can retrieve the policy in all states via the corresponding getter: getPasswordPolicy()

To change the password, use the modifyPassword() methods. These come in two forms: one which takes only the new password and another that takes both the current and the new password as parameter. The first one can be used only if the data vault is unlocked already. The second one is a shorthand for an unlock + password change operation.

The key thing is that the data vault always enforces the compliance of the set password with respect to the configured policy. This is done as soon as possible. Consider the below example:

  1. The data vault is locked and encrypted with the password: P4sSw0rD1
  2. The password policy is changed using setPasswordPolicy() and now it requires that the password contain at least one special character (i.e. one of ~!@#$%^&*()-+). At this moment, the currently set password does not adhere to the new rules.
  3. The next time the data vault is unlocked using P4sSw0rD1 the incompatibility of this password with respect to the new policy is detected. This yields a DataVaultException thrown by unlock().
  4. The application needs to catch this exception, check its error code and if it indicates that there's a password incompatibility then it must redirect the user to a password change screen.
  5. After the new password has been entered the modifyPassword() method can be used to do the modification.

It is highly encouraged to read through the API documentation of the corresponding methods to fully understand when and how password management related problems are reported and how does the data vault behave in the different states and use cases.

UI flows

The DataVault library does not itself contain any UI elements that can be readily used. This is because the requirements may vary greatly from application to application. Instead, application developers are encouraged to build their own UIs and use this library underneath these interfaces.

The security of the data vault depends on how your application manages the password. In most cases, the user needs to enter the password when required. Therefore, you must carefully design the UI interactions related to password management and integrate them into the general flow of your app.

It is not uncommon for certain applications to acquire the password from some other sources. For example, you can implement an app that obtains the data vault password from an external source, a server, another application, etc.. Moreover, you can choose to hard-code the password in your app but doing so is discouraged as it pretty much defeats the whole purpose of data vault encryption.

The below guidelines apply to the most common scenario when you'd like the end-user to specify the password through the UIs you create for your app.

We recommend going through the below checklist to integrate the data vault-related UIs into your app, ensuring that it smoothly blends in with the real functionality:

  1. How many data vaults should I use?

More often than not the answer is 1 or very close to it. An application interacting with certain backend servers usually has to persist a single set of credentials. A single data vault instance (private or shared, depending on your choice) should be enough.

However, if you'd like to build in-app multi-user functionality, it might be a good idea to associate one data vault for each user provided that there are indeed user-specific sensitive data that you would like to store encrypted. Switching between users in this case simply boils down to unlocking the data vault of the corresponding user.

Note that the data vault itself does not have any sort of multi-user feature set out of the box. The above is merely a recommendation should you need to build such functionality into your app.

  1. When should I create data vaults?

The need for creating a data vault arises as soon as you begin to work with sensitive information that you would like to store encrypted. Do you have a welcome screen or a login screen to start your app with? If so, then after you made the end-user complete the login flow, present them with a data vault creation screen.

On this screen, ask for the password to protect the application with. If you already have a login screen and don't want to complicate things, you can right away ask for the password to be used before the login process even starts.

With the sensitive information and the user-specified password at hand, you can create the data vault of the required type using createVault().

  1. When should I lock and unlock the vault?

The rule of thumb is that the data vault should be kept unlocked when the application is in the foreground and kept locked when it goes to the background.

We suggest taking a look at the android.app.Application.ActivityLifecycleCallbacks interface. This can be used to track when your activities transfer between foreground/background states but you can use whatever mechanism for this purpose that suits your needs.

What matters is that you catch the user before it attempts to use any functionality of your app that would require an unlocked data vault, divert them to an unlock screen, ask for the password and then call unlock() on the data vault instance to make it ready for use.

A data vault will lock itself after a certain period of inactivity (depending on the policy) but only when one of its methods that require an unlocked vault is invoked. Locking after a timeout therefore does not happen automatically. This is because the library intends to hand this responsibility to the application developer instead of maintaining a watchdog in the background.

If you'd like to see what the current state of the data vault is, just poke it with the isLocked() method which will auto-lock the vault if the timeout has expired.

  1. Prepare for data vault self-destruction

The data vault destroys itself if it is attempted to be unlocked with the wrong password too many times. The application needs to prepare for this event any time when unlocking the vault with unlock() or with the 2-argument version of modifyPassword().

If the data vault indeed self-destructed then you must think through what kind of information you stored in there. In most cases, the data found in the vault is crucial to the normal operation of the app. Most likely, you'll have to reset the app and redirect the user to the very beginning.

For example, an application might use an encrypted database (for ex. SQLCipher) to store business data (like OData entities) in a secure storage. If you'd like, you can keep the encryption key of this database in a data vault. If the user forgets the data vault password or enters it incorrectly too many times then the data vault gets destroyed and, with it, the database encryption key. So, you'll have to destroy the secured database as well.

How to handle the case when the data vault self-destructs heavily depends on what you used the data vault for. Make sure to design this part of your app carefully and that you take good care of the user should this happen.

The above checklist helps you design and integrate the DataVault library into your app. Depending on your choices, using the data vault might not be as simple as just invoking a few methods to store some data with. Especially, if you rely on user-entered passwords then the screen flow of your application might have to be altered at certain points to ensure that sensitive data is well protected. Security is a cross-cutting concern after all and this library intends to guide you on how to tailor it to the specific needs of your application.