oblador / react-native-keychain

:key: Keychain Access for React Native
MIT License
3.17k stars 520 forks source link

Android UI freezing on launch #314

Open jenskuhrjorgensen opened 4 years ago

jenskuhrjorgensen commented 4 years ago

Hi

I'm experiencing problems with react-native-keychain on Android which unfortunately forces me to disable the library for Android (on iOS it works flawlessly). Sometimes the UI freezes for several seconds shortly after launching the app. This happens when simply importing the library without even using it, as in the following code:

// App.js
import React, {Component} from 'react';
import {TextInput,} from 'react-native';
import 'react-native-keychain';

export default class KeychainExample extends Component {
  render() {
    return (
      <TextInput
        style={{flex: 1}}
      />
    );
  }
}

As you can see in the screen recording below, the UI freezes and ignores any input for several seconds shortly after reloading the app.

freeze

Logcat shows a couple of errors, but I don't know if they are related:

2020-03-15 18:15:01.464 19347-19347/com.keychainexample E/SpannableStringBuilder: SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length
2020-03-15 18:15:01.464 19347-19347/com.keychainexample E/SpannableStringBuilder: SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length
2020-03-15 18:15:02.585 19347-19397/com.keychainexample D/OpenGLRenderer: endAllActiveAnimators on 0x7e77934600 (AlertController$RecycleListView) with handle 0x7e885a9f60
2020-03-15 18:15:02.603 19347-20435/com.keychainexample D/DecorView: onWindowFocusChangedFromViewRoot hasFocus: true, DecorView@7311aa4[MainActivity]
2020-03-15 18:15:02.757 19347-19347/com.keychainexample D/ReactNative: ReactInstanceManager.onJSBundleLoadedFromServer()
2020-03-15 18:15:02.758 19347-19347/com.keychainexample D/ReactNative: ReactInstanceManager.recreateReactContextInBackground()
2020-03-15 18:15:02.758 19347-19347/com.keychainexample D/ReactNative: ReactInstanceManager.runCreateReactContextOnNewThread()
2020-03-15 18:15:02.758 19347-19347/com.keychainexample D/ReactNative: ReactInstanceManager.tearDownReactContext()
2020-03-15 18:15:02.761 19347-19347/com.keychainexample D/ReactNative: CatalystInstanceImpl.destroy() start
2020-03-15 18:15:02.762 19347-19347/com.keychainexample W/unknown:ReactNative: Packager connection already open, nooping.
2020-03-15 18:15:02.766 19347-32636/com.keychainexample D/ReactNative: ReactInstanceManager.createReactContext()
2020-03-15 18:15:02.766 19347-32574/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTEventEmitter.receiveEvent([3,"topBlur",{"target":3}])
2020-03-15 18:15:02.766 19347-32574/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTEventEmitter.receiveEvent([3,"topEndEditing",{"text":"","target":3}])
2020-03-15 18:15:02.771 19347-32637/com.keychainexample V/RNKeychainManager: warming up started at 229678618570351
2020-03-15 18:15:02.771 19347-32636/com.keychainexample D/ReactNative: Initializing React Xplat Bridge.
2020-03-15 18:15:02.772 19347-32636/com.keychainexample D/ReactNative: Initializing React Xplat Bridge before initializeBridge

    --------- beginning of system
2020-03-15 18:15:02.773 19347-32637/com.keychainexample D/RNKeychainManager: Probe cipher storage: CipherStorageFacebookConceal
2020-03-15 18:15:02.773 19347-32637/com.keychainexample D/RNKeychainManager: Probe cipher storage: CipherStorageKeystoreAesCbc
2020-03-15 18:15:02.775 19347-32636/com.keychainexample D/ReactNative: Initializing React Xplat Bridge after initializeBridge
2020-03-15 18:15:02.776 19347-32636/com.keychainexample D/ReactNative: CatalystInstanceImpl.runJSBundle()
2020-03-15 18:15:02.776 19347-32639/com.keychainexample D/ReactNative: ReactInstanceManager.setupReactContext()
2020-03-15 18:15:02.776 19347-32639/com.keychainexample D/ReactNative: CatalystInstanceImpl.initialize()
2020-03-15 18:15:02.778 19347-32639/com.keychainexample D/ReactNative: ReactInstanceManager.attachRootViewToInstance()
2020-03-15 18:15:02.788 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.art.ARTGroupViewManager
2020-03-15 18:15:02.788 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.art.ARTGroupShadowNode
2020-03-15 18:15:02.789 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.art.ARTShapeViewManager
2020-03-15 18:15:02.789 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.art.ARTShapeShadowNode
2020-03-15 18:15:02.790 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.art.ARTTextViewManager
2020-03-15 18:15:02.790 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.art.ARTTextShadowNode
2020-03-15 18:15:02.791 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.checkbox.ReactCheckBoxManager
2020-03-15 18:15:02.792 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.uimanager.LayoutShadowNode
2020-03-15 18:15:02.793 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.picker.ReactDialogPickerManager
2020-03-15 18:15:02.795 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.drawer.ReactDrawerLayoutManager
2020-03-15 18:15:02.795 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.picker.ReactDropdownPickerManager
2020-03-15 18:15:02.796 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.scroll.ReactHorizontalScrollViewManager
2020-03-15 18:15:02.797 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.scroll.ReactHorizontalScrollContainerViewManager
2020-03-15 18:15:02.797 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.progressbar.ReactProgressBarViewManager
2020-03-15 18:15:02.799 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.progressbar.ProgressBarShadowNode
2020-03-15 18:15:02.799 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.scroll.ReactScrollViewManager
2020-03-15 18:15:02.801 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.slider.ReactSliderManager
2020-03-15 18:15:02.801 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.slider.ReactSliderManager$ReactSliderShadowNode
2020-03-15 18:15:02.802 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.switchview.ReactSwitchManager
2020-03-15 18:15:02.803 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.switchview.ReactSwitchManager$ReactSwitchShadowNode
2020-03-15 18:15:02.803 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.swiperefresh.SwipeRefreshLayoutManager
2020-03-15 18:15:02.804 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.art.ARTSurfaceViewManager
2020-03-15 18:15:02.804 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.art.ARTSurfaceViewShadowNode
2020-03-15 18:15:02.804 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.text.frescosupport.FrescoBasedReactTextInlineImageViewManager
2020-03-15 18:15:02.805 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.text.frescosupport.FrescoBasedReactTextInlineImageShadowNode
2020-03-15 18:15:02.805 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.image.ReactImageManager
2020-03-15 18:15:02.806 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.modal.ReactModalHostManager
2020-03-15 18:15:02.807 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.modal.ModalHostShadowNode
2020-03-15 18:15:02.807 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.text.ReactRawTextManager
2020-03-15 18:15:02.807 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.text.ReactRawTextShadowNode
2020-03-15 18:15:02.808 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.textinput.ReactTextInputManager
2020-03-15 18:15:02.810 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.textinput.ReactTextInputShadowNode
2020-03-15 18:15:02.812 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.text.ReactTextViewManager
2020-03-15 18:15:02.813 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.text.ReactTextShadowNode
2020-03-15 18:15:02.813 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.view.ReactViewManager
2020-03-15 18:15:02.813 19347-19452/com.keychainexample D/ReactNative: CatalystInstanceImpl.destroy() end
2020-03-15 18:15:02.815 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.viewpager.ReactViewPagerManager
2020-03-15 18:15:02.815 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.text.ReactVirtualTextViewManager
2020-03-15 18:15:02.815 19347-32639/com.keychainexample W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.text.ReactVirtualTextShadowNode
2020-03-15 18:15:02.841 19347-19347/com.keychainexample W/unknown:ReactNative: Packager connection already open, nooping.
2020-03-15 18:15:02.889 19347-32637/com.keychainexample D/RNKeychainManager: Probe cipher storage: CipherStorageKeystoreRsaEcb
2020-03-15 18:15:03.148 19347-32638/com.keychainexample I/ReactNativeJS: Running "KeychainExample" with {"rootTag":311}
2020-03-15 18:15:03.247 19347-19672/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.247 19347-19449/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.248 19347-19864/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.248 19347-19828/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.249 19347-20528/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.249 19347-20458/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.249 19347-20415/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.249 19347-19958/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.250 19347-20621/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.250 19347-20591/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.250 19347-20561/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.251 19347-27463/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.252 19347-20687/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.253 19347-27632/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.253 19347-27591/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.254 19347-27549/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.254 19347-27510/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.254 19347-27864/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.254 19347-27667/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.255 19347-27646/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.255 19347-28207/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.255 19347-28057/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.256 19347-27935/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.256 19347-27900/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.256 19347-28771/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.257 19347-28615/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.257 19347-28571/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.257 19347-28799/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.257 19347-28872/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.258 19347-28966/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.258 19347-32597/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-start\",\"body\":{\"isInitialUpdate\":true}}","type":"text","id":1}])
2020-03-15 18:15:03.259 19347-19672/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.259 19347-19449/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.259 19347-19864/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.259 19347-19864/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.260 19347-19828/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.260 19347-19828/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.261 19347-20458/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.261 19347-20458/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.261 19347-20528/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.261 19347-20528/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.261 19347-19958/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.261 19347-19958/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.262 19347-20621/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.262 19347-20621/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.262 19347-20415/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.262 19347-20415/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.262 19347-20591/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.262 19347-20591/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.263 19347-20561/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.263 19347-20561/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.263 19347-27463/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.263 19347-27463/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.263 19347-20687/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.263 19347-20687/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.264 19347-27632/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.264 19347-27632/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.264 19347-27591/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.264 19347-27591/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.264 19347-27549/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.265 19347-27549/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.265 19347-27510/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.265 19347-27510/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.265 19347-27864/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.265 19347-27864/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.265 19347-27667/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.266 19347-27667/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.266 19347-27646/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.266 19347-27646/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.266 19347-28207/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.266 19347-28207/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.266 19347-28057/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.267 19347-28057/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.267 19347-27935/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.267 19347-27935/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.267 19347-27900/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.267 19347-27900/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.267 19347-28771/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.268 19347-28771/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.268 19347-28615/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.268 19347-28615/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.269 19347-28571/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.269 19347-28571/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.269 19347-28799/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.269 19347-28799/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.270 19347-28872/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.270 19347-28872/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.271 19347-28966/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.271 19347-28966/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.271 19347-32597/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update\",\"body\":{\"revisionId\":\"e18a6388909dd3d1\",\"isInitialUpdate\":true,\"added\":[],\"modified\":[],\"deleted\":[]}}","type":"text","id":1}])
2020-03-15 18:15:03.271 19347-32597/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.271 19347-19672/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.272 19347-19449/com.keychainexample W/unknown:ReactNative: Calling JS function after bridge has been destroyed: RCTDeviceEventEmitter.emit(["websocketMessage",{"data":"{\"type\":\"update-done\"}","type":"text","id":1}])
2020-03-15 18:15:03.677 19347-19347/com.keychainexample E/SpannableStringBuilder: SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length
2020-03-15 18:15:03.682 19347-19347/com.keychainexample I/chatty: uid=10440(com.keychainexample) identical 2 lines
2020-03-15 18:15:03.682 19347-19347/com.keychainexample E/SpannableStringBuilder: SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length
2020-03-15 18:15:16.947 19347-32637/com.keychainexample D/RNKeychainManager: Selected storage: CipherStorageKeystoreRsaEcb
2020-03-15 18:15:16.966 19347-32637/com.keychainexample W/CipherStorageBase: StrongBox security storage is not available.
    android.security.keystore.StrongBoxUnavailableException: Failed to generate key pair
        at android.security.keystore.AndroidKeyStoreKeyPairGeneratorSpi.generateKeystoreKeyPair(AndroidKeyStoreKeyPairGeneratorSpi.java:511)
        at android.security.keystore.AndroidKeyStoreKeyPairGeneratorSpi.generateKeyPair(AndroidKeyStoreKeyPairGeneratorSpi.java:470)
        at java.security.KeyPairGenerator$Delegate.generateKeyPair(KeyPairGenerator.java:727)
        at com.oblador.keychain.cipherStorage.CipherStorageKeystoreRsaEcb.generateKey(CipherStorageKeystoreRsaEcb.java:256)
        at com.oblador.keychain.cipherStorage.CipherStorageBase.tryGenerateStrongBoxSecurityKey(CipherStorageBase.java:444)
        at com.oblador.keychain.cipherStorage.CipherStorageBase.generateKeyAndStoreUnderAlias(CipherStorageBase.java:391)
        at com.oblador.keychain.KeychainModule.internalWarmingBestCipher(KeychainModule.java:167)
        at com.oblador.keychain.KeychainModule.lambda$NuQDyTTfZc67dTNiVeEDbYNRCJw(Unknown Source:0)
        at com.oblador.keychain.-$$Lambda$KeychainModule$NuQDyTTfZc67dTNiVeEDbYNRCJw.run(Unknown Source:2)
        at java.lang.Thread.run(Thread.java:764)
     Caused by: android.security.KeyStoreException: No StrongBox available
        at android.security.keystore.AndroidKeyStoreKeyPairGeneratorSpi.generateKeystoreKeyPair(AndroidKeyStoreKeyPairGeneratorSpi.java:511) 
        at android.security.keystore.AndroidKeyStoreKeyPairGeneratorSpi.generateKeyPair(AndroidKeyStoreKeyPairGeneratorSpi.java:470) 
        at java.security.KeyPairGenerator$Delegate.generateKeyPair(KeyPairGenerator.java:727) 
        at com.oblador.keychain.cipherStorage.CipherStorageKeystoreRsaEcb.generateKey(CipherStorageKeystoreRsaEcb.java:256) 
        at com.oblador.keychain.cipherStorage.CipherStorageBase.tryGenerateStrongBoxSecurityKey(CipherStorageBase.java:444) 
        at com.oblador.keychain.cipherStorage.CipherStorageBase.generateKeyAndStoreUnderAlias(CipherStorageBase.java:391) 
        at com.oblador.keychain.KeychainModule.internalWarmingBestCipher(KeychainModule.java:167) 
        at com.oblador.keychain.KeychainModule.lambda$NuQDyTTfZc67dTNiVeEDbYNRCJw(Unknown Source:0) 
        at com.oblador.keychain.-$$Lambda$KeychainModule$NuQDyTTfZc67dTNiVeEDbYNRCJw.run(Unknown Source:2) 
        at java.lang.Thread.run(Thread.java:764) 
2020-03-15 18:15:17.065 19347-19347/com.keychainexample W/InputMethodManager: IME died: com.touchtype.swiftkey/com.touchtype.KeyboardService
    android.os.DeadObjectException
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(Binder.java:1179)
        at com.android.internal.view.IInputMethodSession$Stub$Proxy.updateSelection(IInputMethodSession.java:224)
        at android.view.inputmethod.InputMethodManager.updateSelection(InputMethodManager.java:1672)
        at android.widget.Editor.sendUpdateSelection(Editor.java:1749)
        at android.widget.Editor.finishBatchEdit(Editor.java:1589)
        at android.widget.Editor.endBatchEdit(Editor.java:1563)
        at android.widget.TextView.endBatchEdit(TextView.java:8068)
        at com.android.internal.widget.EditableInputConnection.endBatchEdit(EditableInputConnection.java:78)
        at com.android.internal.widget.EditableInputConnection.closeConnection(EditableInputConnection.java:91)
        at com.android.internal.view.IInputConnectionWrapper.executeMessage(IInputConnectionWrapper.java:541)
        at com.android.internal.view.IInputConnectionWrapper.dispatchMessage(IInputConnectionWrapper.java:225)
        at com.android.internal.view.IInputConnectionWrapper.closeConnection(IInputConnectionWrapper.java:211)
        at android.view.inputmethod.InputMethodManager$ControlledInputConnectionWrapper.deactivate(InputMethodManager.java:600)
        at android.view.inputmethod.InputMethodManager.startInputInner(InputMethodManager.java:1334)
        at android.view.inputmethod.InputMethodManager$H.handleMessage(InputMethodManager.java:538)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6898)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
2020-03-15 18:15:17.104 19347-19347/com.keychainexample E/unknown:ReactNative: Got DOWN touch before receiving UP or CANCEL from last gesture
2020-03-15 18:15:20.067 19347-32637/com.keychainexample V/RNKeychainManager: warming up takes: 17296 ms

Tested on OnePlus 5T (A5010) running OxygenOS 9.0.10.

react-native info:
System:
    OS: macOS 10.15.3
    CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
    Memory: 3.50 GB / 32.00 GB
    Shell: 3.2.57 - /bin/bash
  Binaries:
    Node: 10.16.0 - /usr/local/bin/node
    Yarn: 1.19.2 - ~/.yarn/bin/yarn
    npm: 6.13.4 - /usr/local/bin/npm
    Watchman: 4.9.0 - /usr/local/bin/watchman
  SDKs:
    iOS SDK:
      Platforms: iOS 13.2, DriverKit 19.0, macOS 10.15, tvOS 13.2, watchOS 6.1
    Android SDK:
      API Levels: 19, 23, 24, 25, 26, 27, 28, 29
      Build Tools: 23.0.1, 23.0.3, 25.0.1, 25.0.2, 25.0.3, 26.0.0, 26.0.1, 26.0.3, 27.0.3, 28.0.0, 28.0.0, 28.0.2, 28.0.3, 29.0.2
      System Images: android-19 | Google APIs ARM EABI v7a, android-19 | Google APIs Intel x86 Atom, android-25 | Google APIs Intel x86 Atom, android-27 | Google APIs Intel x86 Atom, android-28 | Google APIs Intel x86 Atom, android-29 | Google APIs Intel x86 Atom
  IDEs:
    Android Studio: 3.6 AI-192.7142.36.36.6241897
    Xcode: 11.3.1/11C504 - /usr/bin/xcodebuild
  npmPackages:
    react: 16.9.0 => 16.9.0
    react-native: 0.61.2 => 0.61.2
  npmGlobalPackages:
    react-native-cli: 2.0.1
    react-native-create-library: 3.1.2
    react-native-nemid: 1.13.0

Let me know if you need any more information!

@OleksandrKucherenko I know you have put a lot of effort into this version of the library, especially the Android part. Maybe you have encountered similar issues?

Best regards Jens

OleksandrKucherenko commented 4 years ago

from the provided information, I can assume a long startup time of the keychain library.

errors are not critical, even expected. I log them for informing developers about device capabilities and what configuration of the cypher storage applied.

jenskuhrjorgensen commented 4 years ago

Hi @OleksandrKucherenko

Thank you for your snappy reply!

from the provided information, I can assume a long startup time of the keychain library. Do you mean that the UI is supposed to freeze during startup? Sorry if I misunderstand you.

errors are not critical, even expected. I log them for informing developers about device capabilities and what configuration of the cypher storage applied.

I though so, but wanted to provide them in case someone could use them for something :)

OleksandrKucherenko commented 4 years ago

from the provided information, I can assume a long startup time of the keychain library. Do you mean that the UI is supposed to freeze during startup? Sorry if I misunderstand you.

actually it should not happen, the library did not occupy the main thread (UI thread). initialization is done in a background thread and yes it's slow on the first run, but its the result of the crypto API itself.

OleksandrKucherenko commented 4 years ago
2020-03-15 18:15:03.677 19347-19347/com.keychainexample E/SpannableStringBuilder: SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length
2020-03-15 18:15:03.682 19347-19347/com.keychainexample I/chatty: uid=10440(com.keychainexample) identical 2 lines
2020-03-15 18:15:03.682 19347-19347/com.keychainexample E/SpannableStringBuilder: SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length
2020-03-15 18:15:16.947 19347-32637/com.keychainexample D/RNKeychainManager: Selected storage: CipherStorageKeystoreRsaEcb
2020-03-15 18:15:16.966 19347-32637/com.keychainexample W/CipherStorageBase: StrongBox security storage is not available.

W/- warning, I/ - info, E/ - error, D/ - debug

jenskuhrjorgensen commented 4 years ago

from the provided information, I can assume a long startup time of the keychain library. Do you mean that the UI is supposed to freeze during startup? Sorry if I misunderstand you.

actually it should not happen, the library did not occupy the main thread (UI thread). initialization is done in a background thread and yes it's slow on the first run, but its the result of the crypto API itself.

Yeah I could see that effort had been put into doing the warming up in a background thread, but it doesn't seem to work as intended.

it's slow on the first run

Meaning that it is slow on every first run/every launch of the app?

OleksandrKucherenko commented 4 years ago

Meaning that it is slow on every first run/every launch of the app?

As I said it should not slowdown the UI. Something else is eating the CPU and freezing the UI thread. So far I don't have any clue why it happens. Initialization often takes up to 10 seconds on slow devices.

what device do you use? or it emulator?

jenskuhrjorgensen commented 4 years ago

As stated in the first post (somewhere between all the logs 😄 ) I'm running it on a OnePlus 5T. Are you using the library in some app yourself? And are you not able to reproduce the performance issues that I'm experiencing? What device are you running?

OleksandrKucherenko commented 4 years ago

And are you not able to reproduce the performance issues that I'm experiencing? What device are you running?

during working on PR other devs mention that startup was very slow. and after that report in the PR were added commits with "caching" and "warmup" logic. In test scenario that I use (Example app), startup of the app was never an issue, the first call of the keychain lib was slow, but never a startup of the app!

OleksandrKucherenko commented 4 years ago

https://github.com/oblador/react-native-keychain/blob/f71af4dbab90918f39f3fa3835c9a30b026940ab/android/src/main/java/com/oblador/keychain/KeychainModule.java#L152-L170

check logs for warming up keywords

OleksandrKucherenko commented 4 years ago

https://github.com/oblador/react-native-keychain/blob/f71af4dbab90918f39f3fa3835c9a30b026940ab/android/src/main/java/com/oblador/keychain/KeychainModule.java#L126-L137

and this is the initialization code itself. Inside constructors of the Cyphers only the facebook cypher may have some side effect due to native library loading.

jenskuhrjorgensen commented 4 years ago

https://github.com/oblador/react-native-keychain/blob/f71af4dbab90918f39f3fa3835c9a30b026940ab/android/src/main/java/com/oblador/keychain/KeychainModule.java#L152-L170

check logs for warming up keywords

I did, and in particular this looked extreme: 2020-03-15 18:15:20.067 19347-32637/com.keychainexample V/RNKeychainManager: warming up takes: 17296 ms

jenskuhrjorgensen commented 4 years ago

And are you not able to reproduce the performance issues that I'm experiencing? What device are you running?

during working on PR other devs mention that startup was very slow. and after that report in the PR were added commits with "caching" and "warmup" logic. In test scenario that I use (Example app), startup of the app was never an issue, the first call of the keychain lib was slow, but never a startup of the app!

The app startup time is not that bad, but after using it in my app, I sometimes experienced an unresponsive UI. I could reproduce it in the KeychainExample app by constantly interacting with the UI (e.g. constantly tapping a text input as you can see in the screen recording in the original post), but if I didn't interact with the UI constantly, I wouldn't notice the performance issues. The app renders/launches quick enough.

OleksandrKucherenko commented 4 years ago

I did, and in particular this looked extreme: 2020-03-15 18:15:20.067 19347-32637/com.keychainexample V/RNKeychainManager: warming up takes: 17296 ms

yep... but that a complete module warmup... it triggers maximum things that's are in the module to force java load classes and initialize them. I think you can investigate deeper with android studio profiler and see which method takes the most of the time.

low level crypto/keystores api itself is very slow... I have no idea why it so

maherzaidoune commented 4 years ago

i've faced the same issue, the warming process took long time , that cause UI freezing on low performance phones, i specified the STORAGE_TYPE , (AES in my case)

Screen Shot 2020-04-23 at 12 33 16 AM

and comment the code as in the image

Screen Shot 2020-04-23 at 12 34 39 AM Screen Shot 2020-04-23 at 9 46 56 AM
jenskuhrjorgensen commented 4 years ago

i've faced the same issue, the warming process took long time , that cause UI freezing on low performance phones, i specified the STORAGE_TYPE , (AES in my case)

Screen Shot 2020-04-23 at 12 33 16 AM

and comment the code as in the image

Screen Shot 2020-04-23 at 12 34 39 AM

And did that help on performance or what is your conclusion?

maherzaidoune commented 4 years ago

i've faced the same issue, the warming process took long time , that cause UI freezing on low performance phones, i specified the STORAGE_TYPE , (AES in my case)

Screen Shot 2020-04-23 at 12 33 16 AM

and comment the code as in the image

Screen Shot 2020-04-23 at 12 34 39 AM

And did that help on performance or what is your conclusion?

Well yes, as i'm using RSA ,i didn't need to add Facebook || AES. The last screenshot (just edited my answer), i just needed to create an instance of Cipher , that reduce the warming up time from 20s to 2s (Huawei y7 pro)

pwneth commented 4 years ago

@OleksandrKucherenko have you found any fix for this issue? Still seems to be a problem on older Android devices.

cladjules commented 4 years ago

@OleksandrKucherenko I believe it does block the UI because all the internal function are synchronized and when the main thread tries to call getGenericPassword it waits for the background thread to finish before resuming and allowing the main thread to proceed. I assume that loading the Keystore is much faster in the main thread.

cladjules commented 4 years ago

Personally commenting out the warming makes it much faster,

  /** Allow initialization in chain. */
  public static KeychainModule withWarming(@NonNull final ReactApplicationContext reactContext) {
    final KeychainModule instance = new KeychainModule(reactContext);

    // force initialization of the crypto api in background thread
//    final Thread warmingUp = new Thread(instance::internalWarmingBestCipher, "keychain-warming-up");
//    warmingUp.setDaemon(true);
//    warmingUp.start();

    return instance;
  }

But I need to test on more Android versions.

jenskuhrjorgensen commented 4 years ago

Personally commenting out the warming makes it much faster,

@cladjules And are there any notable drawbacks from removing the warming? Does it still work?

cladjules commented 4 years ago

Personally commenting out the warming makes it much faster,

@cladjules And are there any notable drawbacks from removing the warming? Does it still work?

I have only tried on Android 10, but it's worth testing other versions, use my Fork in your package.json and see if that helps: https://github.com/cladjules/react-native-keychain

cladjules commented 4 years ago

I have tried setting up the Priority to the thread to high, but that didn't help.

I cannot see anything in the warming code that would require the UI Thread, but I assume that Android 10 makes the background threads much slower than the main...

maherzaidoune commented 4 years ago

Personally commenting out the warming makes it much faster,

  /** Allow initialization in chain. */
  public static KeychainModule withWarming(@NonNull final ReactApplicationContext reactContext) {
    final KeychainModule instance = new KeychainModule(reactContext);

    // force initialization of the crypto api in background thread
//    final Thread warmingUp = new Thread(instance::internalWarmingBestCipher, "keychain-warming-up");
//    warmingUp.setDaemon(true);
//    warmingUp.start();

    return instance;
  }

But I need to test on more Android versions.

i tried that before, the UI freezing when i'm requesting keychain for the first time. best solution for me was to edit the warmingUp method and the keyModule method to reduce the code need to be executed.

cladjules commented 4 years ago

Personally commenting out the warming makes it much faster,

  /** Allow initialization in chain. */
  public static KeychainModule withWarming(@NonNull final ReactApplicationContext reactContext) {
    final KeychainModule instance = new KeychainModule(reactContext);

    // force initialization of the crypto api in background thread
//    final Thread warmingUp = new Thread(instance::internalWarmingBestCipher, "keychain-warming-up");
//    warmingUp.setDaemon(true);
//    warmingUp.start();

    return instance;
  }

But I need to test on more Android versions.

i tried that before, the UI freezing when i'm requesting keychain for the first time. best solution for me was to edit the warmingUp method and the keyModule method to reduce the code need to be executed.

Android 10 looks fine though, did you have a freeze on lower versions?

Thanks :)

miikapakarinen commented 4 years ago

We ran into this same issue with several different phone models but it did only occur in Android 10. Android 8 and 9 were not visibly affected.

For example on Samsung A20 Android 10 the warmup time was anything between 5k-40k ms. Downgrading to 4.0.5 solved the issue.

john-y-pazekha commented 4 years ago

@jenskuhrjorgensen @Nullabl3 @cladjules @maherzaidoune @pwneth

Gentlemen, I need your help to reproduce this issue. Since it happens only on specific combination of phone and Android version, I need to know what exactly you're running.

Could you please use this APK and attach here the report it produced? get-device-info.apk.zip

Best regards, John

john-y-pazekha commented 4 years ago

Could you please use this APK and attach here the report it produced? get-device-info.apk.zip

Since a concern was expressed about safety of this APK, I would like to assure you gentlemen that this APK is not malicious. It requests no permissions so it cannot harm your system.

Alternatively, you can clone the source from this repo and run it in Android Studio.

@jenskuhrjorgensen @Nullabl3 @cladjules @maherzaidoune @pwneth Please use either way to produce the log. Without this information we won't be able to reproduce the issue.

jenskuhrjorgensen commented 4 years ago

Hi @john-y-pazekha I must admit, that I also found the APK pretty suspicious, so I actually decided not to install. However, as @oblador vouched good for you I gave it a go, but without success. First Play Protect blocked the installation. I tried this and got past the "blocked-by-play-protect" screen, but now it just says "App not installed" with no explanation. Here are my phone specs: Screenshot_20200529-110633

I also did some more investigation in the performance issues. I tried to disable the warming as suggested by others:

// force initialization of the crypto api in background thread
  //    final Thread warmingUp = new Thread(instance::internalWarmingBestCipher, "keychain-warming-up");
  //    warmingUp.setDaemon(true);
  //    warmingUp.start();

And it helped on the initial freezing, but only delays it for the first time you save credentials. I did some logging and found out that the time consuming task is generator.generateKeyPair().getPrivate(); in CipherStorageKeystoreRsaEcb which takes between 6 and 12 seconds on my OnePlus 5T. The line calls the java crypto API and indicates that the performance issue is in the crypto API itself, which has also been stated by @OleksandrKucherenko .

In CipherStorageKeystoreRsaEcb the library uses java.security.KeyPairGenerator to generate the keys instead of javax.crypto.KeyGenerator which is suggested in Android's biometric auth tutorial, and I'm not sure/smart enough to know why that is, but maybe javax.crypto.KeyGenerator is faster than java.security.KeyPairGenerator.

john-y-pazekha commented 4 years ago

but now it just says "App not installed" with no explanation

@jenskuhrjorgensen I updated the APK, could you please try again? Sorry for the inconvenience.

cladjules commented 4 years ago

Hi @john-y-pazekha I must admit, that I also found the APK pretty suspicious, so I actually decided not to install. However, as @oblador vouched good for you I gave it a go, but without success. First Play Protect blocked the installation. I tried this and got past the "blocked-by-play-protect" screen, but now it just says "App not installed" with no explanation. Here are my phone specs: Screenshot_20200529-110633

I also did some more investigation in the performance issues. I tried to disable the warming as suggested by others:

// force initialization of the crypto api in background thread
  //    final Thread warmingUp = new Thread(instance::internalWarmingBestCipher, "keychain-warming-up");
  //    warmingUp.setDaemon(true);
  //    warmingUp.start();

And it helped on the initial freezing, but only delays it for the first time you save credentials. I did some logging and found out that the time consuming task is generator.generateKeyPair().getPrivate(); in CipherStorageKeystoreRsaEcb which takes between 6 and 12 seconds on my OnePlus 5T. The line calls the java crypto API and indicates that the performance issue is in the crypto API itself, which has also been stated by @OleksandrKucherenko .

In CipherStorageKeystoreRsaEcb the library uses java.security.KeyPairGenerator to generate the keys instead of javax.crypto.KeyGenerator which is suggested in Android's biometric auth tutorial, and I'm not sure/smart enough to know why that is, but maybe javax.crypto.KeyGenerator is faster than java.security.KeyPairGenerator.

You need to use a KeyPair key in order to encrypt the key with fingerprint, otherwise, it would prompt the fingerprint when both encrypting/decrypting which is not ideal.

strawberry-code commented 4 years ago

Same here, with a Samsung J5 (other devices are okay).

strawberry-code commented 4 years ago

For the sake of completeness:

from the provided information, I can assume a long startup time of the keychain library. errors are not critical, even expected. I log them for informing developers about device capabilities and what configuration of the cypher storage applied.

@OleksandrKucherenko

We are encountering this problem also with other plugins:

  1. react-native-fingerprint-scanner
  2. react-native-keychain
  3. react-native-sensitive-info
  4. react-native-biometrics

different plugin, same device, same issue.

Long start warmup, after a while, sensor become responsive.

strawberry-code commented 4 years ago

A bit investigation on adb log we found:

...
RNKeychainManager: warming up started at 829860884663821
...
RNKeychainManager: warming up takes: 28312 ms
...

🤯

strawberry-code commented 4 years ago

We realized that the problem was only in react-native-keychain, while for the other plugins the problem does not exist. Our problem is that we did the tests with the other plugins, without first having unlinked RNKeychain.

Now, we have unlinked RNKeychain and are using RN Sensitive Info and on the Samsung J5 we no longer experience warmup delays. So wa are refactoring the entire App to use RN Sensitive Info only.

OleksandrKucherenko commented 4 years ago

A bit investigation on adb log we found:


...
RNKeychainManager: warming up started at 829860884663821
...
RNKeychainManager: warming up takes: 28312 ms
...

RNKeychainManager: warming up takes: 28312 ms
🤯

for background thread it can be ok to be a little slower, but not 28 seconds... this is tooo long

strawberry-code commented 4 years ago

I agree, we noticed this very long warmup time only on Samsung J5 and no on other Android devices.

hwti commented 4 years ago

I see the same issue on Essential Phone PH-1 (Android 10 GSI build, ie AOSP on June 2020 tags) with the latest release of Mattermost (https://github.com/mattermost/mattermost-mobile).

With Mattermost 1.31.2, using react-native-keychain 4.0.5 : no issue.

With Mattermost 1.32.0, using react-native-keychain 6.0.0 :

jenskuhrjorgensen commented 4 years ago

@john-y-pazekha Sorry for the late response - I just became a dad, so I've been afk for three weeks :D

Here are the results:

{
  "BOARD": "msm8998",
  "CPU_ABI2": "",
  "HOST": "rd-build-103",
  "SUPPORTED_64_BIT_ABIS": [
    "arm64-v8a"
  ],
  "CPU_ABI": "arm64-v8a",
  "PERMISSIONS_REVIEW_REQUIRED": true,
  "DISPLAY": "ONEPLUS A5010_43_200601",
  "SUPPORTED_ABIS": [
    "arm64-v8a",
    "armeabi-v7a",
    "armeabi"
  ],
  "FINGERPRINT": "OnePlus/OnePlus5T/OnePlus5T:10/QKQ1.191014.012/2006012146:user/release-keys",
  "PRODUCT": "OnePlus5T",
  "ID": "QKQ1.191014.012",
  "TYPE": "user",
  "SERIAL": "unknown",
  "DEVICE": "OnePlus5T",
  "TIME": 1591020189000,
  "MODEL": "ONEPLUS A5010",
  "MANUFACTURER": "OnePlus",
  "USER": "jenkins",
  "BRAND": "OnePlus",
  "SUPPORTED_32_BIT_ABIS": [
    "armeabi-v7a",
    "armeabi"
  ],
  "HARDWARE": "qcom",
  "IS_DEBUGGABLE": false,
  "BOOTLOADER": "unknown",
  "RADIO": "unknown",
  "UNKNOWN": "unknown",
  "IS_EMULATOR": false,
  "TAGS": "release-keys"
}
maherzaidoune commented 4 years ago

@john-y-pazekha Sorry for the late response - I just became a dad, so I've been afk for three weeks :D

Here are the results:

{
  "BOARD": "msm8998",
  "CPU_ABI2": "",
  "HOST": "rd-build-103",
  "SUPPORTED_64_BIT_ABIS": [
    "arm64-v8a"
  ],
  "CPU_ABI": "arm64-v8a",
  "PERMISSIONS_REVIEW_REQUIRED": true,
  "DISPLAY": "ONEPLUS A5010_43_200601",
  "SUPPORTED_ABIS": [
    "arm64-v8a",
    "armeabi-v7a",
    "armeabi"
  ],
  "FINGERPRINT": "OnePlus/OnePlus5T/OnePlus5T:10/QKQ1.191014.012/2006012146:user/release-keys",
  "PRODUCT": "OnePlus5T",
  "ID": "QKQ1.191014.012",
  "TYPE": "user",
  "SERIAL": "unknown",
  "DEVICE": "OnePlus5T",
  "TIME": 1591020189000,
  "MODEL": "ONEPLUS A5010",
  "MANUFACTURER": "OnePlus",
  "USER": "jenkins",
  "BRAND": "OnePlus",
  "SUPPORTED_32_BIT_ABIS": [
    "armeabi-v7a",
    "armeabi"
  ],
  "HARDWARE": "qcom",
  "IS_DEBUGGABLE": false,
  "BOOTLOADER": "unknown",
  "RADIO": "unknown",
  "UNKNOWN": "unknown",
  "IS_EMULATOR": false,
  "TAGS": "release-keys"
}

Congrats 🥳

aeirola commented 4 years ago

Experiencing the same issue on our app as well. Managed to work around the issue by disabling autolinking and adding the non warmed up module to my app.

Would be nice if the library would support loading it without necessarily performing the warmup on start, and then allow developers to explicitly control when to perform the warmup with some warmup() function.

For anyone wanting to do a similar workaround, these are roughly the changes I did.

react-native.config.js: Disable autolinking

module.exports = {
  dependencies: {
    'react-native-keychain': { platforms: { android: null } },
  },
};

settings.gradle: Add the project

include ':react-native-keychain'
project(':react-native-keychain').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keychain/android')

app/build.gradle: Add the dependency

dependencies {
    implementation project(':react-native-keychain')
}

ColdKeychaingPackage.java: Separate non-warmupping package

package example;

import androidx.annotation.NonNull;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.Collections;
import java.util.List;

import com.oblador.keychain.KeychainModule;

@SuppressWarnings("unused")
public class ColdKeychainPackage implements ReactPackage {

  public ColdKeychainPackage() {

  }

  @Override
  @NonNull
  public List<NativeModule> createNativeModules(@NonNull final ReactApplicationContext reactContext) {
    return Collections.singletonList(new KeychainModule(reactContext));
  }

  @NonNull
  public List<Class<? extends JavaScriptModule>> createJSModules() {
    return Collections.emptyList();
  }

  @Override
  @NonNull
  public List<ViewManager> createViewManagers(@NonNull final ReactApplicationContext reactContext) {
    return Collections.emptyList();
  }
}

MainApplication.java: Add the cold keychain package

        @Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
          // Packages that cannot be autolinked yet can be added manually here, for example:
          // packages.add(new MyReactNativePackage());
          packages.add(new ColdKeychainPackage());
          return packages;
        }
giregk commented 4 years ago

Hello, I have taken a great inspiration from this module to write my own (because I like to understand stuff). Thank you by the way for your work !

I didn't investigate this issue in particular, but I was surprised when I saw the warming up mechanism in the code. This mechanism generates a "warmingUp" key which has no other purpose.

I don't know if you are aware, but if the chosen algorithm is RSA, it will take a lot of time to generate a key pair. Just try to generate an RSA key pair in your terminal with ssh-keygen, you'll see that takes a noticeable time. Generating an RSA key pair is a random process. The algorithm tests a lot of random numbers until it finds a prime one that's big enough and then derives the private and the public key from it. So the generation can be quick or really long. It both depends on the speed of your processor and on luck.

I believe this slowness issue will be solved once you remove your warmup mechanism as @cladjules commented. And from my understanding of the library, it has no other side effect.

You could also use the AES algorithm instead of the RSA one. It's as secure for this use case. (But generating a key in the Keystore with the option "setUserAuthenticationRequired(true)" and immediately use it to encrypt the user password will fail if the user does not authenticate in between. I guess that's precisely why the RSA algorithm was used in the first place).

I hope this helps.

SudoPlz commented 3 years ago

Just to make things clear, after reading this issue, it looks like our workarounds for now are:

1) Use AES somehow instead of RSA OR 2) Use the non-warming-up version instead of auto linking, until this is officially resolved somehow?

Did I understand that correctly? Thanks

giregk commented 3 years ago

@SudoPlz Yes. Patch the package as suggested above to remove the initial freeze, or force the use of AES by using the STORAGE_TYPE option. The problem is that the AES option is incompatible with the user interaction requirement (biometric or password).

Under the hood

To improve on my previous comment, here is what actually happens. A secure hardware generates the keys: either a secret symmetric key (AES) or a private-public key pair (RSA) in our case. That AES key or RSA public key is used to encrypt your item value and store the encrypted result on a public file. The security lies in the fact that the secure hardware makes sure that only your app can retrieve the key. You can also specify options so that the secure hardware reveals the key only after the user has authenticated with biometrics or password.

The problem on Android is that if you specify that option for an AES key, to be able to encrypt your entry value with the newly created key, you would have to authenticate. So the user experience would be weird: the user types his password, then has to use his fingerprint or device password right after so the app can save the password... That is the reason why, I suppose, the authors of the library decided that in order to enable this option, the best way was to generate an RSA key pair. Indeed, the public key of the RSA key pair is always available... since it is public.

Then came the drawback that generating an RSA key pair is a random process, so it sometimes takes a long time. I suppose the authors of the library thought the problem was the time it took to load the library, and decided to add a warming up mechanism, which is of course not a solution and makes the problem even worse.

There are several alternatives here: 1/ keeping the current RSA implementation and accepting the key generation time randomness (maybe adding a message for the user) 2/ using an AES key without the biometric/password requirement at the hardware level while still requiring it programmatically (perfect UX, but lower theoretical security though still very good) 3/ using an AES key with the biometric/password requirement at the hardware level (highest security) with a UX compromise (requiring authentication right after key generation)

In any case, the warming up mechanism should be removed.

I have implemented 2/ on my project with a simplified api. I can send you the code if you wish.

SudoPlz commented 3 years ago

@giregk thank you SOOOO much for explaining, this is perfect!!!

Ok so then I guess here's a scenario, for an app with low security needs, a user types in their username and password, and for whatever reason we wish to save those credentials.

We also wish to retrieve that username and password only after they authenticate via biometrics (mostly because it's easier and more convenient than typing a password - because that could happen many times).

Does that mean we can't use solution no2 which seems great for us as well? Also yes, if you could share with us your no2 configuration that would be awesome.

Here are my 2 configs currently:

public static NO_BIOMETRICS_CONFIG: Keychain.Options = {
  ...Platform.select({
    android: {
      accessControl: Keychain.ACCESS_CONTROL.APPLICATION_PASSWORD,
    },
    ios: {}
  }),
  accessible: Keychain.ACCESSIBLE.ALWAYS,
  securityLevel: Keychain.SECURITY_LEVEL.ANY,
  storage: Keychain.STORAGE_TYPE.FB // using FB storage to prevent huge RSA creation times, we don't need RSA since we won't be using biometrics on this one
};

public static RAW_CREDS_CONFIG: Keychain.Options = {
  accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY,
  accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED,
  authenticationPrompt: {
    title: 'Unlock to log in',
  },
  // probably uses RSA since we're using BIOMETRY_ANY ?
};

The NO_BIOMETRICS_CONFIG is for saving access tokens or other stuff, so we don't care about biometrics there, since it's not using RSA or AES so it's fast already correct.

The RAW_CREDS_CONFIG is what should be the one that's slowing us down, so that's probably what needs to change to AES.

p.s: I find it super hard to figure out what the differences between FB, RSA and AES are and when to choose each one for this library, are those documented anywhere?

giregk commented 3 years ago

@SudoPlz

About FB vs RSA vs AES

You can find a lot more details on the web about these two algorithms.

The lib chooses the algorithm for you depending on your parameters if you don't specify it.

About your use case

Your NO_BIOMETRICS_CONFIG should not specify the storage type to let the lib decide for you. I can't remember exactly how the code decides, but I think it won't use RSA unless you specify the biometric requirement. (But I'm not sure)

In the case of your RAW_CREDS_CONFIG, my solution 2/ is fine. Biometrics is not really a security improvement anyways, so let's consider it simply as a a means to improve the UX. Here is the code I use in my project: https://github.com/giregk/react-native-simple-keychain. The api is a bit different though. Do read the native code, it will help you.

SudoPlz commented 3 years ago

@giregk I really appreciate the explanation.

So it sounds like I should use AES for both my configurations

That being said, I was under the impression that in order to use your 2nd solution with AES, we can't use biometrics (accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY).

So how can I achieve the following scenario:

A) User types credentials B) Storing credentials to keychain using AES (no biometrics prompts) C) User attempts to load those credentials (Biometrics prompt here) D) AES is used to decrypt, and the credentials are returned

is the following config the right way to do that, or will that result in biometrics being asked after the user types their credentials and tries to save them?

const RAW_CREDS_CONFIG: Keychain.Options = {
    accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY,
    accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED,
    authenticationPrompt: {
      title: 'Unlock to log into Acuity',
    },
    storage: Keychain.STORAGE_TYPE.AES // <-- does aes work with biometrics?
  };

then

await Keychain.setGenericPassword( // <-- NO biometrics requested here
      username,
      password,
      RAW_CREDS_CONFIG,
)
// ...
await Keychain.getGenericPassword(RAW_CREDS_CONFIG); // <-- biometrics requested here
giregk commented 3 years ago

@SudoPlz Try that, but I don't know if that will work. I think it will not. The only way to achieve the desired scenario without having the random freezing time on android is by changing the native implementation of the lib, like in my repo.

=== EDIT It does work :)

SudoPlz commented 3 years ago

@giregk so for now:

are those 2 assumptions correct?

giregk commented 3 years ago

@SudoPlz yes

=== EDIT

...assuming you use the lib as is. If you rewrite the lib from scratch, AES can work.

=== EDIT 2 Wrong! as shown in below comment

SudoPlz commented 3 years ago

@giregk do you know which line of code stops this library from using AES with biometrics? Or from a high level what needs to change to achieve that?