Technical Articles
Getting Started with Kapsel – Part 5 — Encrypted Storage(SP13+)
The Encrypted Storage plugin provides an asynchronous API to store key value pairs securely. The API is based on the web storage interface but is asynchronous in nature. The Logon plugin also provides get and set methods that can be used to store user names, password, keys and certificates while the Encrypted Storage plugin is better suited to storing application data.
For additional details see C:\SAP\MobileSDK3\KapselSDK\docs\api\sap.EncryptedStorage.html or Using the Encrypted Storage Plugin.
The following steps will demonstrate this plugin.
- In the folder C:\Kapsel_Projects\KapselGSDemo add the encrypted storage plugin.
cordova plugin add kapsel-plugin-encryptedstorage --searchpath %KAPSEL_HOME%/plugins or cordova plugin add kapsel-plugin-encryptedstorage --searchpath $KAPSEL_HOME/plugins
- Replace www\index.html with with the following content.
<html> <head> <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width"> <script type="text/javascript" src="cordova.js"></script> <script type="text/javascript" charset="utf-8" src="serverContext.js"></script> <script> window.onerror = onError; keyTextField = ""; valueTextField = ""; resultsTextArea = ""; storeName = "testStore"; store = null; function onError(msg, url, line) { var idx = url.lastIndexOf("/"); var file = "unknown"; if (idx > -1) { file = url.substring(idx + 1); } alert("An error occurred in " + file + " (at line # " + line + "): " + msg); return false; //suppressErrorAlert; } function init() { if (sap.Logger) { sap.Logger.setLogLevel(sap.Logger.DEBUG); //enables the display of debug log messages from the Kapsel plugins. sap.Logger.debug("Log level set to DEBUG"); } if (navigator.notification) { // Override default HTML alert with native dialog. alert is not supported on Windows window.alert = navigator.notification.alert; } keyTextField = document.getElementById("keyTextField"); valueTextField = document.getElementById("valueTextField"); resultsTextArea = document.getElementById("resultsTextArea"); var passcodePolicy = { "expirationDays":"0", "hasDigits":"false", "hasLowerCaseLetters":"false", "hasSpecialLetters":"false", "hasUpperCaseLetters":"false", "defaultAllowed":"true", "lockTimeout":"5", "minLength":"6", "minUniqueChars":"0", "retryLimit":"5" }; //Used if the application is not registering with the SMP 3.0 server. New to SP03. sap.Logon.initPasscodeManager(successCallback, errorCallback, appId, null, passcodePolicy, context); //Used if the application registers with the SMP 3.0 server //sap.Logon.init(successCallback, errorCallback, appId, context); createStore(); } function createStore() { store = new sap.EncryptedStorage(storeName); //SP03 the Encrypted Storage plugin requires logon. API changed no password required. //store = new sap.EncryptedStorage(storeName, "securePassword123"); //SP02 } function deleteStore() { store.deleteStore(successCallback, errorCallback); } function successCallback(info) { console.log("Success: " + JSON.stringify(info)); } function errorCallback(errorInfo) { alert("Error: " + JSON.stringify(errorInfo)); } function setItem(key, value) { store.setItem(key, value, successCallback, errorCallback); } function getItem(key) { store.getItem(key, function(value) { console.log(value); resultsTextArea.value = resultsTextArea.value + key + ":" + value + "\n"; }, errorCallback); } function setItemLogon(key, value) { sap.Logon.set(successCallback, errorCallback, key, value); } function getItemLogon(key) { sap.Logon.get(function(value) { console.log(value); resultsTextArea.value = resultsTextArea.value + key + ":" + value + "\n"; }, errorCallback, key); } function removeItem(key) { store.removeItem(key, successCallback, errorCallback); } function clearAll() { store.clear(successCallback, errorCallback); } function showAll() { resultsTextArea.value = ""; store.length(function(length) { for (var i = 0; i < length; i++) { store.key(i, function(key) { getItem(key); }, errorCallback); } }, errorCallback); } document.addEventListener("deviceready", init, false); </script> </head> <body> <h1>EncryptedStorage Sample</h1> <button id="setItem" onclick="setItem(keyTextField.value, valueTextField.value)">Set Item</button> <button id="getItem" onclick="resultsTextArea.value='';getItem(keyTextField.value)">Get Item</button> <button id="removeItem" onclick="removeItem(keyTextField.value)">Remove Item</button> <button id="clearAll" onclick="clearAll()">Clear All Items</button> <button id="showAll" onclick="showAll()">Show All Items</button> <button id="deleteStore" onclick="deleteStore()">Delete Store</button> <button id="createStore" onclick="createStore()">Create Store</button><br><br> <button id="setItemLogon" onclick="setItemLogon(keyTextField.value, valueTextField.value)">Set Item from Logon</button> <button id="getItemLogon" onclick="resultsTextArea.value='';getItemLogon(keyTextField.value)">Get Item from Logon</button> <button id="lockDV" onclick="sap.Logon.lock(successCallback, errorCallback)">Lock Data Vault</button> <button id="unlockDV" onclick="sap.Logon.unlock(successCallback, errorCallback)">Unlock Data Vault</button> <button id="deletePCM" onclick="sap.Logon.deletePasscodeManager(successCallback, errorCallback)">Delete PasscodeManager</button> <button id="initPCM" onclick="init()">Init PasscodeManager</button> <button id="dataVaultState" onclick="sap.Logon.core.getState(function(value) {alert(JSON.stringify(value))}, errorCallback)">Show Data Vault state</button><br> Key:<input type="text" id="keyTextField"><br> Value:<input type="text" id="valueTextField"><br> <textarea rows="10" cols="30" id="resultsTextArea"></textarea><br> </body> </html>
- Notice that the API is asynchronous. This can make it a bit more challenging to work with. The article Asynchronous JS: Callbacks, Listeners, Control Flow Libs and Promises provides some suggestions on how to work with asynchronous methods.
- Prepare, build and deploy the app with the following command.
cordova run android or cordova run windows -- --archs=x64 or cordova run windows --device -- --archs=arm --phone or cordova run ios
- If the Logging plugin is added and the log level set to debug, the messages logged by the Encrypted Storage plugin can be viewed. The log tag it uses is SMP_ENCRYPTED_STORAGE.
- The data vault of the Logon plugin is required by the Encrypted Storage plugin. The Logon plugin can be initialized using the method sap.Logon.initPasscodeManager. This is to be used instead of sap.Logon.init if the application is not registering against an SAP Mobile Platform. Note that in the case where a registration is not being performed, the passcode policy must be supplied as it is not provided by a SAP Mobile Platform or SAP Cloud Platform Mobile Services server.If the Logon plugin’s data vault is deleted, the EncryptedStorage plugin will also delete all storage as well. This can occur when the user clicks the Forgot Application Passcode button on the unlock screen of the Logon plugin, if the user enters too many incorrect passcodes or if the method sap.Logon.core.deleteRegistration is called.
- The following are some technical details of where the data is stored on Android.
Key value pairs are stored in a SQLLite Database.
The database is created using local storage which can only be accessed by the application that created it. The file is stored under /data/data/packageName and can be seen and accessed when using an emulator but not a device.The values and the keys stored in this SQLLite database are encrypted. See Encrypted Storage Plugin for additional technical details on the encryption used to store the data.
How does this work in combination with Odata offline? Can I use this to encrypt my data in the offline store?
The Encrypted Storage plugin and the offline OData feature are separate. I updated the Offline section of this guide to include some info on how to encrypt the offline database. https://blogs.sap.com/2017/01/24/getting-started-with-kapsel-part-10-offline-odatasp13/#encrypt
Hope that helps,
Dan van Leeuwen
Thank you for a very informative blog on Encrypted Storage. A quick question though ...
We have a complex structure of oData with multiple navigation and 1..n relationships that are being persisted via CREATE_DEEP_ENTITY operation in ONLINE mode.
In OFFLINE application, we are saving the same complex structure (multiple entries) in EncryptedStorage and show it to user in 'Outbox'. Than we synchronise the Outbox and persist the data with CREATE_DEEP_ENTITY once user comes online. Is this the right usage of EncryptedStorage for storing such data or are we missing something here? As far as I know multiple navigation with 1...n is not yet supported with oData offline.
Thanks.
Hello,
I have seen something weird. Unlocked data vaults for sap.Logon.init and sap.Logon.initPasscodeManager are different in IOS.
If I set a key when I initialize my app with sap.Logon.init I can't read it later on when I start my application with sap.Logon.initPasscodeManager.
This just happens on IOS, in Android it's working fine and I have already enabled keychain groups. What can be wrong?
I believe the intention is that your app would either register with an SAP Mobile Platform or SAP Cloud Platform Mobile Services server in which case it would use sap.Logon.init or if the application is not registering with a server than use sap.Logon.initPasscodeManager.
To be clear, I don't think you should be mixing the two methods. Is there a reason why you are doing so?
Regards,
Dan van Leeuwen
The reason is we want to register with a server (.init) if the device is online, however, if we are offline, we need to access the datavault without registering to a server.
My main concern is why is it working on android, but not on iOS.
The first time the app is opened, it must be online for the offline store to be downloaded from the server. From then on, the device may be offline. Calling sap.Logon.init will succeed when the device is offline after the initial registration. I believe the intended usage of sap.Logon.initPasscodeManager is for apps that do not register with an SAP Mobile Platform or Clould Platform Mobile Services server.
Regards,
Dan van Leeuwen