public final class

SecureDatabaseStore

extends Object
java.lang.Object
   ↳ com.sap.cloud.mobile.foundation.securestore.SecureDatabaseStore

Class Overview

Secure Database Store provides transparent 256-bit AES encryption of data stored in a SQLite database.

Creating

To instantiate a SecureDatabaseStore, you need to specify the Android application context, database file name, database version, and provide an instance of CreateDatabaseCallback, where the database table is created or updated.
static final int DATABASE_VERSION = 1; // Increment this number when the table schema changes.
     final String databaseName = "myDatabase";
     final String schema =
        "CREATE TABLE IF NOT EXISTS users "
        + "(name TEXT, age INTEGER, email TEXT NOT NULL UNIQUE PRIMARY KEY)";

     SecureDatabaseStore store = new SecureDatabaseStore(
        context,
        databaseName,
        DATABASE_VERSION,
        new CreateDatabaseCallback() {
            public void onCreate(SecureDatabaseStore store) {
                store.executeUpdate(schema); // Executes the CREATE TABLE statement
            }

            public void onUpgrade(SecureDatabaseStore store, int oldVersion, int newVersion) {
                // Modify the existing table or migrate the original table data based on the
                // differences across each version.
                if (oldVersion < 2) {
                    ...
                }
                if (oldVersion < 3) {
                    ...
                }
                // Now the app will iterate over the update statements and run any that are needed.
                // No matter what previous version was and regardless of what more recent version
                // they upgrade to, the app will run the proper statements to take the app from the
                // older schema to the properly upgraded one.
            }
        });
  

Opening

You open the store with an encryption key before you can interact with it. It is recommended to use EncryptionUtil to obtain the encryption key. If this is the first time store is being opened and null is provided, an encryption key will be generated transparently and will be used for subsequent opens.

The store fails to open if there are insufficient resources or permissions to open and/or create the store or the encryption key provided by the caller is incorrect.

try {
        byte[] myEncryptionKey = EncryptionUtil.getEncryptionKey("aliasForMyDatabase", myPasscode);
        store.open(myEncryptionKey);
        // Or use the auto-generated encryption key by passing null.
        // store.open(null);
    } catch (OpenFailureException ex) {
        logger.error("An error occurred while opening the database.", ex);
        // Some recovery here. For example, re-get the encryption key from the end user via UI...
    } catch (EncryptionError ex) {
	    logger.error("Failed to get encryption key.", ex);
    }
  

Executing Updates

Any SQL statement that is not a SELECT statement qualifies as an update, including CREATE, UPDATE, INSERT, ALTER, COMMIT, BEGIN, DETACH, DELETE, DROP, END, EXPLAIN, VACUUM, and REPLACE statements (and many more).
Basically, if your SQL statement does not begin with SELECT, it is an update statement.

Executing updates can throw errors if something fails:

// Inserts entries.
     final String insertSQL = "INSERT INTO users (email, name, age) VALUES(?,?,?)";

     try {
        store.executeUpdate(insertSQL,     // INSERT statement with parameters '?'.
            "john.smith@company.com",      // Email.
            "John Smith",                  // Name.
            39);                           // Age.

        store.executeUpdate(insertSQL, "mary.clark@company.com", "Mary Clark", 21);
     } catch (BackingStoreException ex) {
        logger.error("Failed to insert.", ex);
     }
  

Executing Queries

A SELECT statement is a query and executes via one of the executeQuery(...) methods. Executing query returns a SecureDatabaseResultSet object if successful, and throws an error upon failure.

You typically use a while() loop to iterate over the results of the query. You also need to step from one record to the next:

// Queries the database and iterates through the rows in the result set.
    final String queryAllUsers = "SELECT * FROM users";

    try (SecureDatabaseResultSet rs = store.executeQuery(queryAllUsers)) {
        while (rs.next()) {
            // Has a valid row, retrieves the column values with appropriate getter method.
            int age = rs.getInt("age");
            String name = rs.getString("name");
            String email = rs.getString("email");
            logger.debug("Retrieved user: age = {}, name = {}, email = {}", age, name, email);
        }
    } catch (BackingStoreException ex) {
        logger.error("Failed to execute query.", ex);
    }
  
You must always invoke next() before attempting to access the values returned in a query, even if you are only expecting one row in the result set:
int userCount = -1;

     try (SecureDatabaseResultSet rs =  store.executeQuery("SELECT COUNT(*) AS count FROM users")) {
        if (rs.next()) {
            userCount = rs.getInt(0);
            logger.debug("Number of users: {}", userCount);
        }
     } catch (BackingStoreException ex) {
        logger.error("Failed to get user count.", ex);
     }
  

SecureDatabaseResultSet

A SecureDatabaseResultSet contains many methods for retrieving different data types in an appropriate format.

SecureDatabaseResultSet needs to be closed. It will be closed automatically if you use a try-with block. Otherwise, you need to call close() method explicitly.

Changing Encryption Key

You can change the encryption of the underlying persistence store when the store is in open state.
store.changeEncryptionKey(newEncryptionKey);

 

Closing Database

When you finish executing queries and updates on the database, close() the SecureDatabaseStore to relinquishes any resources it has acquired during its operations.

Multiple Statements and Batch Commands

You can use executeStatements(String) to execute multiple SQL statements, those statements will be executed in a transaction internally, that is, if one or more of the SQL statements failed, the transaction will be rolled back and act like no statements are executed.
try {
        store.executeStatements(
            "CREATE TABLE test1 (id INTEGER PRIMARY KEY AUTOINCREMENT, x TEXT)",
            "CREATE TABLE test2 (id INTEGER PRIMARY KEY AUTOINCREMENT, y TEXT)",
            "CREATE TABLE test3 (id INTEGER PRIMARY KEY AUTOINCREMENT, z TEXT)",
            "INSERT INTO test1 (x) VALUES ('XXX')",
            "INSERT INTO test2 (y) VALUES ('YYY')",
            "INSERT INTO test3 (z) VALUES ('ZZZ')"
        );
     } catch (BackingStoreException | TransactionFailureException ex) {
        logger.error("An error occurred while executing SQL statements on the database.", ex);
     }
  

Data Sanitization

When providing a SQL statement, use the standard SQLite binding syntax. Use placeholders '?' for values to be inserted, updated or used in WHERE clauses in SELECT statements.

Do not construct the SQL statement from variables like this:

String sql = "INSERT INTO myTable VALUES (" + value1 + ", " + value2 + ", " + value3 + ")";
  

Instead, use the standard SQLite binding syntax like this:

String sql = "INSERT INTO myTable VALUES (?, ?, ?)";

     try {
        store.executeUpdate(sql, value1, value2, value3);
     } catch (BackingStoreException ex) {
        logger.error("An error occurred while executing a SQL statement.", ex);
     }
  

The '?' character is recognized by SQLite as a placeholder for a value to be inserted. The execution methods all accept a representation of varargs (basically an array of arguments).

Removing the database

You may want to remove the database in some occasions. Note that after the database is removed, any further operation performed on the instance will cause FileMissingException.
store.deleteStore(context); // The database will not be usable after this method!
 
 

Summary

Public Constructors
SecureDatabaseStore(Context context, String databaseName, int databaseVersion, CreateDatabaseCallback cb)
Constructs a Secure Database Store and invokes the provided onCreate(SecureDatabaseStore) for creating database objects if those objects do not exist already, or invokes onUpgrade(SecureDatabaseStore, int, int) if the database version is newer than the existing database.
Public Methods
void beginExclusiveTransaction()
Begins a transaction.
void changeEncryptionKey(byte[] newEncryptionKey)
Changes the encryption key of the database.
void close()
Closes the database.
void commit()
Commits the transaction, and ends the current transaction.
void deleteStore(Context context)
Deletes the database file.
void executeInsert(String tableName, ContentValues values)
Executes an INSERT statement by filling the column names and the associated values in a ContentValues object.
SecureDatabaseResultSet executeQuery(String sql)
Executes a SELECT statement that does not contains parameters ('?').
SecureDatabaseResultSet executeQuery(String sql, String... values)
Executes a SELECT statement that contains parameters ('?').
void executeStatements(String... sqlStatements)
Executes multiple SQL statements, each SQL statement does not contain parameters ('?').
void executeUpdate(String sql, Object... values)
Executes a non-SELECT statement that contains parameters ('?').
void executeUpdate(String sql)
Executes a non-SELECT statement that does contain parameters ('?').
SQLiteDatabase getDatabase()
Returns the underlying net.sqlcipher.database.SQLiteDatabase instance assuming SecureDatabaseStore(Context, String, int, CreateDatabaseCallback) and open(byte[]) have been called.
boolean isInTransaction()
Checks if a transaction is in progress.
boolean isOpen()
Checks if the database exists and has been opened, that is, open(byte[]) )} has been called and close() has not been called.
void open(byte[] encryptionKey)
Opens the encrypted database with the encryption key.
void rollback()
Rolls back the current open transaction.
synchronized boolean storeExists()
Checks if the database file exists.
[Expand]
Inherited Methods
From class java.lang.Object

Public Constructors

public SecureDatabaseStore (Context context, String databaseName, int databaseVersion, CreateDatabaseCallback cb)

Constructs a Secure Database Store and invokes the provided onCreate(SecureDatabaseStore) for creating database objects if those objects do not exist already, or invokes onUpgrade(SecureDatabaseStore, int, int) if the database version is newer than the existing database.

Note: The caller must call open(byte[]) after a SecureDatabaseStore instance is created. Otherwise, all accesses to the instance will cause FileClosedException.

Parameters
context Android application context
databaseName a non-empty name of the Database that can only contain -, _, a-z, A-Z, 0-9 and ..
cb callback that is used to to specify table schema to create or update the database objects
Throws
IllegalArgumentException If database name is empty or invalid, of the database version < 1

Public Methods

public void beginExclusiveTransaction ()

Begins a transaction.

Throws
FileClosedException if the database has been closed.

public void changeEncryptionKey (byte[] newEncryptionKey)

Changes the encryption key of the database.

Database should have been opened with the old encryption key.

Parameters
newEncryptionKey new encryption key. It is recommended to use EncryptionUtil to obtain the new encryption key. If null is provided, an encryption key will be generated transparently and will be used for subsequent opens.
Throws
FileClosedException if the database has been closed.
EncryptionError if the database failed to generate a new encryption key.

public void close ()

Closes the database. Note that once the database is closed, no operations are allowed.

public void commit ()

Commits the transaction, and ends the current transaction.

Throws
FileClosedException if the database has been closed.
TransactionFailureException if an error occurs during commit.

public void deleteStore (Context context)

Deletes the database file. Note that this instance is no longer usable after the call, any operation performed on the instance will get FileMissingException.

Parameters
context Android application context

public void executeInsert (String tableName, ContentValues values)

Executes an INSERT statement by filling the column names and the associated values in a ContentValues object.

Example:

static final String TABLE_NAME = "account_user";
    static final String COLUMN_EMAIL = "email";
    static final String COLUMN_FULL_NAME = "fullName";
    static final String COLUMN_ACCOUNT_NAME = "accountName";

    // Fills each column with value.
    ContentValues cv = new ContentValues();
    cv.put(COLUMN_EMAIL, "john.smith@mycompany.com");
    cv.put(COLUMN_FULL_NAME, "John Smith");
    cv.put(COLUMN_ACCOUNT_NAME, "JSmith032");

    // databaseStore was defined, instantiated and opened already.
    databaseStore.executeInsert(TABLE_NAME, cv);

 

Parameters
tableName table name
values a ContentValues object that contains column names and the associated values
Throws
FileClosedException if the database has been closed.
BackingStoreException if the row insertion fails.

public SecureDatabaseResultSet executeQuery (String sql)

Executes a SELECT statement that does not contains parameters ('?').

As a good practice, use ERROR(/#executeQuery(String, String...)) if possible.

Parameters
sql a SELECT statement
Returns
  • a non-nullresult set from the query which may or may not contain rows.
Throws
FileClosedException if the database has been closed.
BackingStoreException if the query fails.

public SecureDatabaseResultSet executeQuery (String sql, String... values)

Executes a SELECT statement that contains parameters ('?').

Parameters
sql a SELECT statement
values one or more String values to match the '?' in the SQL statement
Returns
  • a non-nullresult set from the query which may or may not contain rows.
Throws
FileClosedException if the database has been closed.
BackingStoreException if the query fails.

public void executeStatements (String... sqlStatements)

Executes multiple SQL statements, each SQL statement does not contain parameters ('?'). The multiple SQL statements will be executed inside a transaction, any SQL statement execution failure will cause the changes to be rolled back.

Parameters
sqlStatements a vararg (array) of SQL statements
Throws
FileClosedException if the database has been closed.
BackingStoreException if the statement fails to execute.
TransactionFailureException if transaction fails.

public void executeUpdate (String sql, Object... values)

Executes a non-SELECT statement that contains parameters ('?').

Any SQL statement that is not a SELECT statement qualifies as an update, including CREATE, UPDATE, INSERT, ALTER, COMMIT, BEGIN, DETACH, DELETE, DROP, END, EXPLAIN, VACUUM, and REPLACE statements and many more. Basically, if your SQL statement does not begin with SELECT, it is an update statement.

Parameters
sql a valid SQL statement that does not begin with SELECT and contains '?'
values array of objects to match the '?'s in the SQL statement
Throws
FileClosedException if the database has been closed.
BackingStoreException if the non-SELECT statement fails to execute.

public void executeUpdate (String sql)

Executes a non-SELECT statement that does contain parameters ('?').

Any SQL statement that is not a SELECT statement qualifies as an update, including CREATE, UPDATE, INSERT, ALTER, COMMIT, BEGIN, DETACH, DELETE, DROP, END, EXPLAIN, VACUUM, and REPLACE statements and many more. Basically, if your SQL statement does not begin with SELECT, it is an update statement.

As a good practice, should use ERROR(/#executeUpdate(String, Object...)) if possible.

Parameters
sql a SQL statement that does not contains parameters ('?')
Throws
FileClosedException if the database has been closed.
BackingStoreException if the update failed.

public SQLiteDatabase getDatabase ()

Returns the underlying net.sqlcipher.database.SQLiteDatabase instance assuming SecureDatabaseStore(Context, String, int, CreateDatabaseCallback) and open(byte[]) have been called.

Returns
  • The underlying net.sqlcipher.database.SQLiteDatabase instance, or null if this instance of SecureDatabaseStore is not opened, that is, either open(byte[]) was not called or close() has been called.

public boolean isInTransaction ()

Checks if a transaction is in progress.

Returns
  • true if a transaction is in progress, false otherwise.

public boolean isOpen ()

Checks if the database exists and has been opened, that is, open(byte[]) )} has been called and close() has not been called.

Returns
  • true if the database is open, false otherwise.

public void open (byte[] encryptionKey)

Opens the encrypted database with the encryption key.

You must open the store before you can interact with it. The store can fail to open if the encryption key is incorrect. When the store is opened for the first time, the provided key is used to encrypt the database.

Parameters
encryptionKey the key to encrypt the store. It is recommended to use EncryptionUtil to obtain the encryption key. If this is the first time store is being opened and null is provided, an encryption key will be generated transparently and will be used for subsequent opens.
Throws
OpenFailureException if the database is not opened successfully possibly due to incorrect encryption key.

public void rollback ()

Rolls back the current open transaction.

Throws
FileClosedException if the database has been closed.
TransactionFailureException if an error occurs during transaction rollback.

public synchronized boolean storeExists ()

Checks if the database file exists.

Returns
  • True if the database file exists, false otherwise.