diff --git a/dist/android/sources b/dist/android/sources
index bd92e40dd32fbf30d3d3428c7b2ef469098ed66f..65fae394ca791b990578f5dde1e49f5e97fd34fa 160000
--- a/dist/android/sources
+++ b/dist/android/sources
@@ -1 +1 @@
-Subproject commit bd92e40dd32fbf30d3d3428c7b2ef469098ed66f
+Subproject commit 65fae394ca791b990578f5dde1e49f5e97fd34fa
diff --git a/package.json b/package.json
index a70fdcb9e495df7f83e10242014b1392b93ffe28..72ccafab02d767fece8978557c6c830ab1377bc7 100644
--- a/package.json
+++ b/package.json
@@ -218,4 +218,4 @@
     "node": ">= 16.17.0",
     "yarn": ">= 1.22.0"
   }
-}
+}
\ No newline at end of file
diff --git a/resources/android/build/app/src/main/java/com/crypho/plugins/SecureStorage.java b/resources/android/build/app/src/main/java/com/crypho/plugins/SecureStorage.java
new file mode 100644
index 0000000000000000000000000000000000000000..aac52bc3529367033901af5048ca544dbea046c9
--- /dev/null
+++ b/resources/android/build/app/src/main/java/com/crypho/plugins/SecureStorage.java
@@ -0,0 +1,392 @@
+package com.crypho.plugins;
+
+import android.annotation.TargetApi;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.Base64;
+import android.util.Log;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaArgs;
+import org.apache.cordova.CordovaPlugin;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.lang.reflect.Method;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+* CHANGES :
+*   31/05/2023 - BLA - Report MR as a workaround for issue #6 (https://github.com/Sotam/cordova-plugin-secure-storage-android10/issues/6)
+*                      TODO: remove this file after new release, with this fixed issue
+*/
+public class SecureStorage extends CordovaPlugin {
+    private static final String TAG = "SecureStorage";
+
+    private static final boolean SUPPORTED = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+    private static final Boolean IS_API_29_AVAILABLE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
+    private static final Integer DEFAULT_AUTHENTICATION_VALIDITY_TIME = 60 * 60 * 24; // Fallback to 24h. Workaround to avoid asking for credentials too "often"
+
+    private static final String MSG_NOT_SUPPORTED = "API 19 (Android 4.4 KitKat) is required. This device is running API " + Build.VERSION.SDK_INT;
+    private static final String MSG_DEVICE_NOT_SECURE = "Device is not secure";
+    private static final String MSG_KEYS_FAILED = "Generate RSA Encryption Keys failed. ";
+
+    private Hashtable<String, SharedPreferencesHandler> SERVICE_STORAGE = new Hashtable<String, SharedPreferencesHandler>();
+    private String INIT_SERVICE;
+    private String INIT_PACKAGENAME;
+    private volatile CallbackContext secureDeviceContext, generateKeysContext, unlockCredentialsContext;
+    private volatile boolean generateKeysContextRunning = false;
+
+    private AbstractRSA rsa = RSAFactory.getRSA();
+
+    @Override
+    public void onResume(boolean multitasking) {
+        if (secureDeviceContext != null) {
+
+            if (isDeviceSecure()) {
+                secureDeviceContext.success();
+            } else {
+                secureDeviceContext.error(MSG_DEVICE_NOT_SECURE);
+            }
+            secureDeviceContext = null;
+        }
+
+        if (unlockCredentialsContext != null) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                cordova.getThreadPool().execute(new Runnable() {
+                    public void run() {
+                        if (unlockCredentialsContext != null) {
+                            String alias = service2alias(INIT_SERVICE);
+                            if (rsa.userAuthenticationRequired(alias)) {
+                                unlockCredentialsContext.error("User not authenticated");
+                            }
+                            unlockCredentialsContext.success();
+                            unlockCredentialsContext = null;
+                        }
+                    }
+                });
+            }
+        }
+    }
+
+    @Override
+    public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException {
+        if (!SUPPORTED) {
+            Log.w(TAG, MSG_NOT_SUPPORTED);
+            callbackContext.error(MSG_NOT_SUPPORTED);
+            return false;
+        }
+        if ("init".equals(action)) {
+            String service = args.getString(0);
+            JSONObject options = args.getJSONObject(1);
+
+            String packageName = options.optString("packageName", getContext().getPackageName());
+
+            Context ctx = null;
+
+            // Solves #151. By default, we use our own ApplicationContext
+            // If packageName is provided, we try to get the Context of another Application with that packageName
+            try {
+                ctx = getPackageContext(packageName);
+            } catch (Exception e) {
+                // This will fail if the application with given packageName is not installed
+                // OR if we do not have required permissions and cause a security violation
+                Log.e(TAG, "Init failed :", e);
+                callbackContext.error(e.getMessage());
+            }
+
+            INIT_PACKAGENAME = ctx.getPackageName();
+            String alias = service2alias(service);
+            INIT_SERVICE = service;
+
+            SharedPreferencesHandler PREFS = new SharedPreferencesHandler(alias, ctx);
+            SERVICE_STORAGE.put(service, PREFS);
+            if (!isDeviceSecure()) {
+                Log.e(TAG, MSG_DEVICE_NOT_SECURE);
+                callbackContext.error(MSG_DEVICE_NOT_SECURE);
+            } else if (!rsa.encryptionKeysAvailable(alias)) {
+                // Encryption Keys aren't available, proceed to generate them
+                Integer userAuthenticationValidityDuration = options.optInt("userAuthenticationValidityDuration", DEFAULT_AUTHENTICATION_VALIDITY_TIME);
+                generateKeysContext = callbackContext;
+                generateEncryptionKeys(userAuthenticationValidityDuration);
+                if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
+                    unlockCredentialsLegacy();
+                }
+            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && rsa.userAuthenticationRequired(alias)) {
+                // User has to confirm authentication via device credentials.
+                String title = options.optString("unlockCredentialsTitle", null);
+                String description = options.optString("unlockCredentialsDescription", null);
+
+                unlockCredentialsContext = callbackContext;
+                unlockCredentials(title, description);
+            } else {
+                initSuccess(callbackContext);
+            }
+
+            return true;
+        }
+        if ("set".equals(action)) {
+            final String service = args.getString(0);
+            final String key = args.getString(1);
+            final String value = args.getString(2);
+            final String cipherMode = args.isNull(3) ? null : args.getString(3); // Close #6 - cipherMode is optional
+            final String adata = service;
+            cordova.getThreadPool().execute(new Runnable() {
+                public void run() {
+                    try {
+                        JSONObject result = AES.encrypt(value.getBytes(), adata.getBytes(), cipherMode);
+                        byte[] aes_key = Base64.decode(result.getString("key"), Base64.DEFAULT);
+                        byte[] aes_key_enc = rsa.encrypt(aes_key, service2alias(service));
+                        result.put("key", Base64.encodeToString(aes_key_enc, Base64.DEFAULT));
+                        if (cipherMode != null) result.put("mode", cipherMode);
+                        getStorage(service).store(key, result.toString());
+                        callbackContext.success(key);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Encrypt failed :", e);
+                        callbackContext.error(e.getMessage());
+                    }
+                }
+            });
+            return true;
+        }
+        if ("get".equals(action)) {
+            final String service = args.getString(0);
+            final String key = args.getString(1);
+            String value = getStorage(service).fetch(key);
+            if (value != null) {
+                JSONObject json = new JSONObject(value);
+                final byte[] encKey = Base64.decode(json.getString("key"), Base64.DEFAULT);
+                final JSONObject data = json.getJSONObject("value");
+                final byte[] ct = Base64.decode(data.getString("ct"), Base64.DEFAULT);
+                final byte[] iv = Base64.decode(data.getString("iv"), Base64.DEFAULT);
+                final byte[] adata = Base64.decode(data.getString("adata"), Base64.DEFAULT);
+                cordova.getThreadPool().execute(new Runnable() {
+                    public void run() {
+                        try {
+                            byte[] decryptedKey = rsa.decrypt(encKey, service2alias(service));
+                            String cipherMode = data.isNull("mode") ? null : data.getString("mode");
+                            String decrypted = new String(AES.decrypt(ct, decryptedKey, iv, adata, cipherMode));
+                            callbackContext.success(decrypted);
+                        } catch (Exception e) {
+                            Log.e(TAG, "Decrypt failed :", e);
+                            callbackContext.error(e.getMessage());
+                        }
+                    }
+                });
+            } else {
+                callbackContext.error("Key [" + key + "] not found.");
+            }
+            return true;
+        }
+        if ("secureDevice".equals(action)) {
+            // Open the Security Settings screen. The app developer should inform the user about
+            // the security requirements of the app and initialize again after the user has changed the screen-lock settings
+            secureDeviceContext = callbackContext;
+            secureDevice();
+            return true;
+        }
+        if ("remove".equals(action)) {
+            String service = args.getString(0);
+            String key = args.getString(1);
+            getStorage(service).remove(key);
+            callbackContext.success(key);
+            return true;
+        }
+        if ("keys".equals(action)) {
+            String service = args.getString(0);
+            callbackContext.success(new JSONArray(getStorage(service).keys()));
+            return true;
+        }
+        if ("clear".equals(action)) {
+            String service = args.getString(0);
+            getStorage(service).clear();
+            callbackContext.success();
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isDeviceSecure() {
+        KeyguardManager keyguardManager = (KeyguardManager) (getContext().getSystemService(Context.KEYGUARD_SERVICE));
+        try {
+            Method isSecure = null;
+            isSecure = keyguardManager.getClass().getMethod("isDeviceSecure");
+            return ((Boolean) isSecure.invoke(keyguardManager)).booleanValue();
+        } catch (Exception e) {
+            return keyguardManager.isKeyguardSecure();
+        }
+    }
+
+    private String service2alias(String service) {
+        String res = INIT_PACKAGENAME + "." + service;
+        return res;
+    }
+
+    private SharedPreferencesHandler getStorage(String service) {
+        return SERVICE_STORAGE.get(service);
+    }
+
+    private void initSuccess(CallbackContext context) {
+        context.success();
+    }
+
+    /**
+     * Create the Confirm Credentials screen.
+     * You can customize the title and description or Android will provide a generic one for you if you leave it null
+     *
+     * @param title
+     * @param description
+     */
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    private void unlockCredentials(final String title, final String description) {
+        cordova.getActivity().runOnUiThread(new Runnable() {
+            public void run() {
+                if (IS_API_29_AVAILABLE && isDeviceSecure()) {
+                    // Building a biometric prompt instance with custom title and description.
+                    BiometricPrompt.Builder biometricPromptBuilder = new BiometricPrompt.Builder(getContext());
+                    biometricPromptBuilder.setTitle(title);
+                    biometricPromptBuilder.setDescription(description);
+                    //biometricPromptBuilder.setDeviceCredentialAllowed(true);
+                    BiometricPrompt biometricPrompt = biometricPromptBuilder.build();
+                    CancellationSignal cancellationSignal = new CancellationSignal();
+                    final Executor executor = getExecutor();
+                    // Launching the credential confirmation popup to get biometric validation.
+                    // If biometric is not available will open the other unlock methods.
+                    biometricPrompt.authenticate(cancellationSignal, executor, new BiometricPrompt.AuthenticationCallback() {
+                        @Override
+                        public void onAuthenticationError(int errorCode, CharSequence errString) {
+                            super.onAuthenticationError(errorCode, errString);
+                        }
+
+                        @Override
+                        public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
+                            super.onAuthenticationHelp(helpCode, helpString);
+                        }
+
+                        @Override
+                        public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
+                            super.onAuthenticationSucceeded(result);
+                        }
+
+                        @Override
+                        public void onAuthenticationFailed() {
+                            super.onAuthenticationFailed();
+                        }
+                    });
+                } else {
+                    KeyguardManager keyguardManager = (KeyguardManager) (getContext().getSystemService(Context.KEYGUARD_SERVICE));
+                    Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(title, description);
+                    if (intent != null) {
+                        startActivity(intent);
+                    } else {
+                        Log.e(TAG, "Error creating Confirm Credentials Intent");
+                        unlockCredentialsContext.error("Cant't unlock credentials, error creating Confirm Credentials Intent");
+                    }
+                }
+            }
+        });
+    }
+
+    @TargetApi(Build.VERSION_CODES.KITKAT)
+    private void unlockCredentialsLegacy() {
+        cordova.getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Intent intent = new Intent("com.android.credentials.UNLOCK");
+                startActivity(intent);
+            }
+        });
+    }
+
+    /**
+     * Generate Encryption Keys in the background.
+     *
+     * @param userAuthenticationValidityDuration User authentication validity duration in seconds
+     */
+    private void generateEncryptionKeys(final Integer userAuthenticationValidityDuration) {
+        if (generateKeysContext != null && !generateKeysContextRunning) {
+            cordova.getThreadPool().execute(new Runnable() {
+                public void run() {
+                    generateKeysContextRunning = true;
+                    try {
+                        String alias = service2alias(INIT_SERVICE);
+                        SharedPreferencesHandler storage = getStorage(INIT_SERVICE);
+                        if(storage.isEmpty()){
+                            //Solves Issue #96. The RSA key may have been deleted by changing the lock type.
+                            getStorage(INIT_SERVICE).clear();
+                            rsa.createKeyPair(getContext(), alias, userAuthenticationValidityDuration);
+                        }
+                        generateKeysContext.success();
+                    } catch (Exception e) {
+                        Log.e(TAG, MSG_KEYS_FAILED, e);
+                        generateKeysContext.error(MSG_KEYS_FAILED + e.getMessage());
+                    } finally {
+                        generateKeysContext = null;
+                        generateKeysContextRunning = false;
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * Open Security settings screen.
+     */
+    private void secureDevice() {
+        cordova.getActivity().runOnUiThread(new Runnable() {
+            public void run() {
+                try {
+                    Intent intent = new Intent(Settings.ACTION_SECURITY_SETTINGS);
+                    startActivity(intent);
+                } catch (Exception e) {
+                    Log.e(TAG, "Error opening Security settings to secure device : ", e);
+                    secureDeviceContext.error(e.getMessage());
+                }
+            }
+        });
+    }
+
+    private Context getContext() {
+        return cordova.getActivity().getApplicationContext();
+    }
+
+    /**
+     * Creates a executor with handler to run runnable tasks.
+     */
+    private Executor getExecutor() {
+        return new Executor() {
+            @Override
+            public void execute(Runnable command) {
+                Handler handler = new Handler();
+                handler.post(command);
+            }
+        };
+    }
+
+    private Context getPackageContext(String packageName) throws Exception {
+        Context pkgContext = null;
+
+        Context context = getContext();
+        if (context.getPackageName().equals(packageName)) {
+            pkgContext = context;
+        } else {
+            pkgContext = context.createPackageContext(packageName, 0);
+        }
+
+        return pkgContext;
+    }
+
+    private void startActivity(Intent intent) {
+        cordova.getActivity().startActivity(intent);
+    }
+
+}
diff --git a/scripts/env-android.sh b/scripts/env-android.sh
index cf2c6b8319dfb25b790f8ab8ff7969431296817d..ed0ab540430d512612c43d18703f624db228db9a 100755
--- a/scripts/env-android.sh
+++ b/scripts/env-android.sh
@@ -17,7 +17,7 @@ fi
 
 echo "--- Preparing Android environment:"
 echo "        Root: ${PROJECT_DIR}"
-echo "      NodeJS: version ${NODE_VERSION} with options: ${NODE_OPTIONS}"
+echo "      NodeJS: ${NODE_VERSION} with options: ${NODE_OPTIONS}"
 echo " Android SDK: ${ANDROID_SDK_ROOT}"
 echo " Android CLI: ${ANDROID_SDK_CLI_ROOT}"
 echo " Build Tools: ${ANDROID_BUILD_TOOLS_ROOT}"
diff --git a/scripts/env-global.sh b/scripts/env-global.sh
index c66c6aebaefb307ee4b8b786b7c3f6b2f41cfd6e..a393dca59b15c9b5d8b4c40f120e787b98085779 100755
--- a/scripts/env-global.sh
+++ b/scripts/env-global.sh
@@ -20,7 +20,7 @@ REPO="duniter/cesium"
 REPO_API_URL="https://api.github.com/repos/${REPO}"
 REPO_PUBLIC_URL="https://github.com/${REPO}"
 
-NODEJS_VERSION=16
+NODE_VERSION=16
 #NODE_OPTIONS=--max-old-space-size=4096
 IONIC_CLI_VERSION=6.20.9
 
@@ -111,11 +111,11 @@ if test -d "${NVM_DIR}"; then
     . "${NVM_DIR}/nvm.sh"
 
     # Switch to expected version
-    nvm use ${NODEJS_VERSION}
+    nvm use ${NODE_VERSION}
 
     # Or install it
     if test $? -ne 0; then
-        nvm install ${NODEJS_VERSION}
+        nvm install ${NODE_VERSION}
     fi
 else
     echo "nvm (Node version manager) not found (directory ${NVM_DIR} not found). Please install, and retry"
@@ -178,7 +178,7 @@ fi
 export PATH \
   PROJECT_DIR PROJECT_NAME \
   REPO REPO_API_URL REPO_PUBLIC_URL \
-  NODEJS_VERSION \
+  NODE_VERSION \
   JAVA_HOME \
   ANDROID_NDK_VERSION ANDROID_SDK_VERSION ANDROID_SDK_CLI_VERSION \
   ANDROID_HOME ANDROID_SDK_ROOT ANDROID_ALTERNATIVE_SDK_ROOT ANDROID_SDK_CLI_ROOT \
diff --git a/scripts/release.sh b/scripts/release.sh
index eccc9d01f4650b07284ef5659e8f1cde7643e463..23ec02e86b090aa07259e2233a8f0de58a908650 100755
--- a/scripts/release.sh
+++ b/scripts/release.sh
@@ -178,5 +178,5 @@ echo "**********************************"
 cd ${PROJECT_DIR}
 
 # Back to nodejs project version
-nvm use ${NODEJS_VERSION}
+nvm use ${NODE_VERSION}
 
diff --git a/www/js/services/storage-services.js b/www/js/services/storage-services.js
index 4f92253dddd5269e880a68435f3a34f17c6b8176..04f138ae24f66a30cab3c5729d396aba64e0a797 100644
--- a/www/js/services/storage-services.js
+++ b/www/js/services/storage-services.js
@@ -87,16 +87,19 @@ angular.module('cesium.storage.services', [ 'cesium.config'])
     // Set a value to the secure storage (or remove if value is not defined)
     exports.secure.put = function(key, value) {
       return $q(function(resolve, reject) {
-        if (value !== undefined && value !== null) {
+        if (value != null) {
           exports.secure.storage.set(
             function (key) {
               resolve();
             },
             function (err) {
+              $log.error("[storage] Failed to set value for key=" + key);
               $log.error(err);
               reject(err);
             },
-            key, value);
+            key, value,
+            null // Cipher mode  - "CCM" (default) or "GCM" (Galois/Counter Mode)
+          );
         }
         // Remove
         else {
@@ -105,6 +108,7 @@ angular.module('cesium.storage.services', [ 'cesium.config'])
               resolve();
             },
             function (err) {
+              $log.error("[storage] Failed to remove key=" + key);
               $log.error(err);
               resolve(); // Error = not found
             },
@@ -115,6 +119,7 @@ angular.module('cesium.storage.services', [ 'cesium.config'])
 
     // Get a value from the secure storage
     exports.secure.get = function(key, defaultValue) {
+      $log.debug("[storage] Getting value from secure storage, using key=" + key);
       return $q(function(resolve, reject) {
         exports.secure.storage.get(
           function (value) {
@@ -126,6 +131,7 @@ angular.module('cesium.storage.services', [ 'cesium.config'])
             }
           },
           function (err) {
+            $log.error("[storage] Failed to get value from secure storage, using key=" + key);
             $log.error(err);
             resolve(); // Error = not found
           },
@@ -137,11 +143,31 @@ angular.module('cesium.storage.services', [ 'cesium.config'])
     exports.secure.setObject = function(key, value) {
       $log.debug("[storage] Setting object into secure storage, using key=" + key);
       return $q(function(resolve, reject){
-        exports.secure.storage.set(
-          resolve,
-          reject,
-          key,
-          value ? JSON.stringify(value) : undefined);
+        if (value != null) {
+          exports.secure.storage.set(
+            resolve,
+            function(err) {
+              $log.error("[storage] Failed to set object into secure storage, using key=" + key, value);
+              $log.error(err);
+              reject(err);
+            },
+            key,
+            JSON.stringify(value),
+            null // Cipher mode  - "CCM" (default) or "GCM" (Galois/Counter Mode)
+          );
+        }
+        else {
+          exports.secure.storage.remove(
+            function () {
+              resolve();
+            },
+            function (err) {
+              $log.error("[storage] Failed to remove key=" + key);
+              $log.error(err);
+              resolve(); // Error = not found
+            },
+            key);
+        }
       });
     };
 
@@ -150,8 +176,12 @@ angular.module('cesium.storage.services', [ 'cesium.config'])
       $log.debug("[storage] Getting object from secure storage, using key=" + key);
       return $q(function(resolve, reject){
         exports.secure.storage.get(
-          function(value) {resolve(JSON.parse(value||'null'));},
+          function(value) {
+            $log.debug("[storage] Getting object from secure storage, using key=" + key + " - result="+value);
+            resolve(JSON.parse(value||'null'));
+          },
           function(err) {
+            $log.error("[storage] Failed to get object from secure storage, using key=" + key);
             $log.error(err);
             resolve(); // Error = not found
           },