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 iOS application development and Objective-C programming in general.

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 the iOS KeyChain as the storage medium but implements an additional encryption layer on top of it.

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 modes

A data vault is represented by an instance of the DataVault class (defined in the DataVault.h header). This defines the public API used to interact with the encrypted storage underneath. You can obtain an instance of a data vault by using the lifecycle API available in the form of class-level methods.

The library can be configured to create data vaults in one of the below modes:

  • Private: The data vault is private to the application that creates it. No other apps can have access to the data stored in it.
  • Shared: The data vault is shared across a set of applications, all of which might access it.

    This feature is implemented using iOS KeyChain sharing. For it to work, the [DataVault setAccessGroup:] class-level method must be called with the appropriate Keychain Access Group identifier.

    It is recommended to check the official documentation about how to set up an Access Group properly.

The shared mode 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.

Lifecycle

The vault identifier

No matter the configured mode, 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 mode, the scope of the identifier does indeed depend on it:

  • In private mode, identifiers are scoped to the current application.
  • In shared mode, identifiers are shared across applications, therefore the scope of the vault ID is the entire device (more closely, the apps within the same access group signed with the same certificate). If you create a shared data vault in one app then you can open it with the same identifier in another one using the DataVault class (within the same access group, of course).

The lifecycle API

Use the below clas-level methods of DataVault to obtain and manage data vault instances with:

  • setAccessGroup: - Switches the library to shared mode by setting the access group to be used when interacting with the iOS KeyChain.
  • createVault:password: - 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.

Creating a new instance using [[DataVault alloc] init] or [DataVault new] is not supported and might yield undefined behaviour. Always stick to the lifecycle API methods to obtain a DataVault instance.

Notes about removal

As the data vault is built on top of the iOS KeyChain, its contents are not cleared when the application is uninstalled. It is recommended to have some mechanism in place that can detect a reinstall and delete any previously created data vaults left behind by an earlier installation.

This is a known trait of the KeyChain on iOS and is still how the platform works as of the time of writing this guide.

If you don't want a previous data vault to pollute the keychain of your freshly installed app, make sure to detect and delete any lingering data vaults using the deleteVault: method.

Data access

A data vault stores key-value pairs. A key is a string, a value can be either an NSString or an NSData.

The corresponding setters and getters are:

  • setString:value:/getString:
  • setValue:value:/getValue:

The data vault does not allow you to store nil values. If you call the setters with nil 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" value:@"foo123"];
[dv setValue:@"foo" value:[@"bar" dataUsingEncoding:NSUTF8StringEncoding]];

...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 dataNames property which is an array of 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 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 checking the locked property. 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.

You'll find overloaded variants of both lifecycle and data vault methods which also require salts. These are deprecated and are kept only for backward compatibility. Always stick to the ones requiring the password only.

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 API 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 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, update the passwordPolicy property which is doable (with some limitations) even in locked state.

To change the password, use the changePassword:/changePassword:newPassword: 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 passwordPolicy 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 changePassword:newPassword: 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:

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.

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 using createVault:password:.

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.

This can be done by implementing the appropriate callback methods of the UIApplicationDelegate. Check the Apple documentations for the details.

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 by reading the locked property which will auto-lock the vault if the timeout has expired.

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 changePassword:newPassword:.

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.