Secure Store

Secure Storage


The SDK includes a number of components that securely store data. These components differ from each other in several aspects and it is up to you to select the one that best meets your development goals.

Components are distinguished by storage location, encryption mode, API mode (interaction used to store and read data), and that some components support more APIs than others, which affects how data is stored and read back:

  • Storage location – supports file based and keychain based storage.
  • Encryption mode – supports hardcoded AES256 and a customized encryption option.
  • API mode – directly declared APIs, NSCoding based methods, and Apple’s Codable protocol.
  • Convenience – some components use other components for data storage, but declare and/or support other convenience APIs.

Basic Storage Components

  • SecureDatabaseStore: file based encrypted SQL storage with embedded decryption.
  • SecureKeyValueStore: file based encrypted storage using a simplified Key/Value API where Value can be a basic Swift type or any NSCoding compliant type. Supports DataStoring protocol. Technically always assigned to a SecureDatabaseStore and stores the values in a table. It is possible to use one SecureDatabaseStore with more SecureKeyValueStore so even if there are more SecureKeyValueStores all information is stored in one file!
  • KeychainStorage: Keychain based storage supports a simplified Key/Value storage where Value is Data. Allows replacing the encryption module to any custom one (see Ciphering protocol). Supports DataStoring protocol. Since it is based on Keychain, it supports data sharing amongst applications.
  • MemoryDataStorage: memory based storage supports a simplified key/value based API where Value is data. Supports DataStoring protocol

It is possible to use these components directly, however there are other components which provide more convenience APIs to interact with stored types and allow to store data in one or more of the storages. See Convenience storage components.

Convenience Storage Components

  • CodableStorage: supports a Codable compliant API so any type which conforms to the Codable protocol can be stored there. Uses a basic storage component through the DataStoring protocol to store the data and a Coder component to transform the stored type to/from Data.
  • CompositeStorage: supports Codable complient API, but uses two basic storages through the DataStoring protocol to store data and a Coder component to transform the stored type to/from Data. It uses only the first store while the second store is not set and synchronize all data to the second when it is set. If there are two storages assigned, first it tries to provide data from the first store, that’s why the first store is a memory based store usually.

Important Protocols

  • Codable protocol: defined by Apple
  • DataStoring protocol: stores and retrieves a data assigned with a key
  • Coder protocol: transforms an instance of a type to/from Data

What to Use

Check your application’s requirements which storage mechanism is the best for you.

  • When a larger amount of data should be stored, a file based storage is appropriate as a Keychain storage has limitations.
  • When sharing amongst applications is not necessary, use a file based storage as it is removed automatically when the application is removed from the device. In contrast, the Keychain based store remains on the device, so when the application is removed and then installed again on the device, the Keychain data will still be there.
  • When a complex query is needed to get some data the SecureDatabaseStore should be used as it is the only one which supports SQL queries.
  • When data should be shared amongst applications, use the KeychainStorage.

SecureDatabaseStore Migration

In release 3.0 SP03, we have upgraded the underlaying SQLCipher to version 4.0.1. Since this is a major upgrade in SQLCipher, it contains breaking changes, resulting in some incompatibilities with the previous versions. By default, we support using existing databases via SQLCipher’s compatibility mode. However, all new database files are created with the latest SQLCipher defaults.

It might be required to migrate older databases to the latest SQLCipher defaults for increased security, performance and reduced file sizes. See SQLCipher release notes for more information on what has been improved.

The following code snippet showcases how to migrate existing databases.

let databaseURL = <# the path of the database #>
let databaseEncryptionKey = <# the encryption key of the database #>
let db = FMDatabase(url: databaseURL)

guard db.open() else { return }
guard db.setKey(databaseEncryptionKey) else { db.close(); return }

// the migration process results in a single row with a single value in it
// 0 – successful | 1 – failed
// execution time depends on the size of the database and the speed of the device
let result = try db.executeQuery("PRAGMA cipher_migrate;", values: nil)

guard !result.bool(forColumnIndex: 0), db.goodConnection else { 
    // TODO: error handling
    db.close()
    return
}

// success
guard db.close() else { return }

Note: migration might impact SecureKeyValueStores, since they use the SecureDatabaseStore for data management.

  • Implementation for CodableStorage Stores each instance as Data in the store using coder for encoding/decoding. The CodableStorage instance is not thread safe. Using directly from multiple threads can lead to unexpected behaviors.

    See more

    Declaration

    Swift

    open class CodableStorage : CodableStoring
  • Composite Storage

    Represents an in-memory dictionary storage and physical storage. The CompositeStorage instance is not thread safe. Using directly from multiple threads can lead to unexpected behaviors.

    To initialize a default CompositeStorage you would use the following lines somewhere central in your application. Then it’s ready to use.

    var compositeStore = CompositeStorage()
    // by default it is initialized with a MemoryDataStorage() and a PlistCoder()
     do {
     // setting an example store
         let secureKeyValueStore = SecureKeyValueStore()
         try secureKeyValueStore.open(with: "your_encryption_key")
         try self.compositeStore.setPersistentStore(secureKeyValueStorage)
    } catch  {
         //error handling
    }
    
    

    You can insert to the CompositeStorage instance.

    let testCustomObject = CustomTestStruct()
    
    // put data to compositeStore
     do {
         try self.compositeStore.put(testCustomObject, key: "testCustomObject")
         let returnDataFromMemory = try self.compositeStore.get(CustomTestStruct.self, key: "testCustomObject")
     } catch {
         //error handling
     }
    

    Load data from CompositeStorage

     do {
     let returnData = try self.compositeStore.get(CustomTestStruct.self, key: "testCustomObject")
     } catch {
     //error handling
     }
    

    Remove a key from compositeStore

     do {
         try self.compositeStore.removeData(for: key)
     } catch {
         //error handling
     }
    
    See more

    Declaration

    Swift

    open class CompositeStorage : CompositeCodableStoring
  • Memory Data Store

    Represents an in-memory dictionary storage. The MemoryDataStorage instance is not thread safe. Using directly from multiple threads can lead to unexpected behaviors.

    To initialize a default MemoryDataStorage you would use the following lines somewhere central in your application. Then it’s ready to use.

    var memoryDatabaseStore = MemoryDatabaseStore()
    
    

    You can insert to the MemoryDataStorage instance as long as the application is running.

    let stringToEncode = "exampleStringToEncode"
    if let encodedData = stringToEncode.data(using: .utf8) {
        do {
            try self.memoryStore.put(data: encodedData, for: "dataKey")
        } catch {
            // handle error
        }
    }
    
    // Load data from store
    var returnData: Data?
    do {
        returnData = try self.memoryStore.data(for: "encodedDataKey")
    } catch {
        // handle error
    }
    
    // Remove data from store
    var returnData: Data?
    do {
        returnData = try self.memoryStore.data(for: "encodedDataKey")
    } catch {
        // handle error
    }
    
    See more

    Declaration

    Swift

    public class MemoryDataStorage : DataStoring
  • SecureDatabaseStore class

    Represents a single SQLite database, can be used by executing SQL statements. The SecureDatabaseStore instance is not thread safe. Using directly from multiple threads can lead to unexpected behaviors. To use it a thread-safe way use the SecureDatabaseQueue. To initialize a default SecureDatabaseStore you would use the following lines somewhere central in your application as soon as you have an encryption key for that store.

    var secureDatabaseStore = SecureDatabaseStore()
    
    try secureDatabaseStore.open(with: "your_encryption_key")
    

    You can read from and write to the SecureDatabaseStore instance as long as it is not being closed.

    To create the SecureDatabaseStore at a custom location, please use the following code:

    let customDatabasePath = "/your_custom_dir/your_custom_name.db"
    var secureDatabaseStore = SecureDatabaseStore(fullDatabasePath: customDatabasePath)
    
    try secureDatabaseStore.open(with: "your_encryption_key")
    
    See more

    Declaration

    Swift

    public class SecureDatabaseStore
  • Secure Key-Value Store

    Similar to the SecureDatabaseStore, you can create instances of the SecureKeyValueStore as follows.

    let secureKeyValueStore = SecureKeyValueStore()
    
    try secureKeyValueStore.open(with: "your_encryption_key")
    

    It is also possible to create more SecureKeyValueStores sharing the same database file. In this can specify a separate name for the store. Shared the same database is not thread safe. It is the task of the caller to make it thread safe.

    // it is advisable to name all the stores if multiple store used in one file
    let firstStore = SecureKeyValueStore(name:"FirstStore")
    var secondStore = SecureKeyValueStore()
    
    do {
       // initialization throws only when the firstStore is open already
       try SecureKeyValueStore(databaseStore: firstStore.secureDatabaseStore, name:"secondKeyValueStore")
    } catch {
       // handle error
       return
    }
    
    // open one of the stores
    do {
       try firstStore.open(with: "any passcode")
    } catch {
       // handle error
       return
    }
    

    Since the init of the store will throw only when the first database is already open and there isn’t schema created already and the schema creation fails it is possible to create the store with try! Simplified form:

    let firstStore = SecureKeyValueStore(name:"FirstStore")
    let secondStore = try! SecureKeyValueStore(databaseStore: firstStore.secureDatabaseStore, name:"secondKeyValueStore")
    
    // open one of the stores
    do {
       try firstStore.open(with: "any passcode")
    } catch {
       // handle error
       return
    }
    
    See more

    Declaration

    Swift

    open class SecureKeyValueStore : KeyValueStoreProtocol
    extension SecureKeyValueStore: DataStoring
  • Secure Database Queue Store

    If you want to perform multiple database operations concurrently, you can create an instance of the SecureDatabaseQueue as follows.

    var secureDatabaseQueue = SecureDatabaseQueue(databaseStore: secureDatabaseStore)
    
    try secureDatabaseStore.open(with: "your_encryption_key")
    
    See more

    Declaration

    Swift

    public class SecureDatabaseQueue
  • Executing queries returns a SecureDatabaseResultSetProtocol compliant object if successful, and throws an error upon failure.

    You typically use a while() loop to iterate through the results of your query. You also need to “step” from one record to the next. The easiest way to do this is by using this code:

    do {
       let rs: SecureDatabaseResultSetProtocol = try db.executeQuery("SELECT * FROM myTable")
       defer {
           rs.close()
       }
    
       while try rs.next() {
           //retrieve values for each record
       }
    } catch let error {
       logger.error("An error occurred while executing a query on the database.", error: error)
    }
    
    See more

    Declaration

    Swift

    public class SecureDatabaseResultSet
  • SQLiteDatatypeBridgeable is a protocol to which all types that can be stored in a SQLite3 database are compliant.

    Warning

    Do not conform custom types to the SQLiteDatatypeBridgeable protocol.

    The following types are conformant with this protocol:

    Swift Types
    Data
    Date
    Int, Int8-Int64
    UInt, UInt8-UInt64
    Bool
    Float, Double
    String

    Declaration

    Swift

    public protocol SQLiteDatatypeBridgeable
  • CodableStoring API Implementers has to store instances of classes conforming to the Codable protocol associated with a given key.

    See more

    Declaration

    Swift

    public protocol CodableStoring : AnyObject
  • Undocumented

    See more

    Declaration

    Swift

    public protocol CompositeCodableStoring : CodableStoring
  • CoderProtocol API

    See more

    Declaration

    Swift

    public protocol CoderProtocol
  • KeyValueStore API.

    See more

    Declaration

    Swift

    public protocol KeyValueStoreProtocol
  • Enum which covers all Errors occuring in the SecureStorage component.

    See more

    Declaration

    Swift

    public enum SecureStorageError : Error
    extension SecureStorageError: SAPError