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. SupportsDataStoring
protocol. Technically always assigned to aSecureDatabaseStore
and stores the values in a table. It is possible to use oneSecureDatabaseStore
with moreSecureKeyValueStore
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 isData
. Allows replacing the encryption module to any custom one (seeCiphering
protocol). SupportsDataStoring
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. SupportsDataStoring
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 aCodable
compliant API so any type which conforms to theCodable
protocol can be stored there. Uses a basic storage component through theDataStoring
protocol to store the data and aCoder
component to transform the stored type to/from Data.CompositeStorage
: supportsCodable
complient API, but uses two basic storages through theDataStoring
protocol to store data and aCoder
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 SecureKeyValueStore
s, since they use the SecureDatabaseStore
for data management.
-
Implementation for CodableStorage Stores each instance as
See moreData
in thestore
usingcoder
for encoding/decoding. The CodableStorage instance is not thread safe. Using directly from multiple threads can lead to unexpected behaviors.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
See moredo { try self.compositeStore.removeData(for: key) } catch { //error handling }
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.
See morelet 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 }
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:
See morelet customDatabasePath = "/your_custom_dir/your_custom_name.db" var secureDatabaseStore = SecureDatabaseStore(fullDatabasePath: customDatabasePath) try secureDatabaseStore.open(with: "your_encryption_key")
Declaration
Swift
public class SecureDatabaseStore
-
Secure Key-Value Store
Similar to the
SecureDatabaseStore
, you can create instances of theSecureKeyValueStore
as follows.let secureKeyValueStore = SecureKeyValueStore() try secureKeyValueStore.open(with: "your_encryption_key")
It is also possible to create more
SecureKeyValueStore
s 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:
See morelet 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 }
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.
See morevar secureDatabaseQueue = SecureDatabaseQueue(databaseStore: secureDatabaseStore) try secureDatabaseStore.open(with: "your_encryption_key")
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:
See moredo { 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) }
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 moreDeclaration
Swift
public protocol CodableStoring : AnyObject
-
Undocumented
See moreDeclaration
Swift
public protocol CompositeCodableStoring : CodableStoring
-
CoderProtocol API
See moreDeclaration
Swift
public protocol CoderProtocol
-
KeyValueStore API.
See moreDeclaration
Swift
public protocol KeyValueStoreProtocol
-
Enum which covers all Errors occuring in the SecureStorage component.
See moreDeclaration
Swift
public enum SecureStorageError : Error
extension SecureStorageError: SAPError