realm / realm-java

Realm is a mobile database: a replacement for SQLite & ORMs
http://realm.io
Apache License 2.0
11.47k stars 1.75k forks source link

Application crashing after version upgrade (migration) - problem with decryption #6466

Closed KonradSzewczuk closed 5 years ago

KonradSzewczuk commented 5 years ago

Goal

Launch application and be able to decrypt existing database using key received from Android Keystore. It was working this way, but after upgrade of the app version (migration) it is not.

Actual Results

Application is crashing when installing newer version of the app. It is crashing right away after on launch.

io.realm.exceptions.RealmFileException: Unable to open a realm at path '/data/data/com.someapp.tmt.app.verifier/files/default.realm': Realm file decryption failed Path:. (Realm file decryption failed Path: /data/data/com.someapp.tmt.app.verifier/files/default.realm) (/data/data/com.someapp.tmt.app.verifier/files/default.realm) in /Users/cm/Realm/realm-java/realm/realm-library/src/main/cpp/io_realm_internal_OsSharedRealm.cpp line 101 Kind: ACCESS_ERROR.
        at io.realm.internal.OsSharedRealm.nativeGetSharedRealm(Native Method)
        at io.realm.internal.OsSharedRealm.<init>(OsSharedRealm.java:171)
        at io.realm.internal.OsSharedRealm.getInstance(OsSharedRealm.java:241)
        at io.realm.internal.OsSharedRealm.getInstance(OsSharedRealm.java:231)
        at io.realm.RealmCache.doCreateRealmOrGetFromCache(RealmCache.java:318)
        at io.realm.RealmCache.createRealmOrGetFromCache(RealmCache.java:284)
        at io.realm.Realm.getDefaultInstance(Realm.java:407)
        at com.someapp.tmt.app.source.db.RealmManager.obtain(RealmManager.kt:17)
        at com.someapp.tmt.app.source.db.RealmRepository$getListSingle$1.call(RealmRepository.kt:52)
        at com.someapp.tmt.app.source.db.RealmRepository$getListSingle$1.call(RealmRepository.kt:15)
        at io.reactivex.internal.operators.single.SingleDefer.subscribeActual(SingleDefer.java:36)
        at io.reactivex.Single.subscribe(Single.java:2779)
        at io.reactivex.Single.subscribe(Single.java:2765)
        at com.someapp.tmt.app.feature.scanning.observer.CredentialProfilesObserver.getProfilesByIds(CredentialProfilesObserver.kt:36)
        at com.someapp.tmt.app.feature.scanning.observer.CredentialProfilesObserver.loadProfiles(CredentialProfilesObserver.kt:20)
        at com.someapp.tmt.app.feature.scanning.ScanningPresenter.loadCredentialProfiles(ScanningPresenter.kt:164)
        at com.someapp.tmt.app.feature.scanning.ScanningPresenter.syncWithConfiguration(ScanningPresenter.kt:154)
        at com.someapp.tmt.app.feature.scanning.ScanningPresenter.onViewCreated(ScanningPresenter.kt:103)
        at com.someapp.tmt.app.mvp.MvpActivity.onPostCreate(MvpActivity.kt:48)
        at com.someapp.tmt.app.feature.scanning.ScanningActivity.onPostCreate(ScanningActivity.kt:93)
        at android.app.Instrumentation.callActivityOnPostCreate(Instrumentation.java:1207)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2909)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2988)
        at android.app.ActivityThread.-wrap14(ActivityThread.java)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1631)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6682)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)

Steps & Code to Reproduce

Below is the configuration used for initializing Realm in the Application class (only onCreate() method):

 @Inject
  lateinit var realmManager: RealmManager
 @Inject
  lateinit var realmEncryptionHelper: RealmEncryptionHelper

  override fun onCreate() {
    realmManager.init(this, realmEncryptionHelper.loadKey(this))
    setupStetho()
    entryLogger.start()
  }

2.

Below is the RealmManager class:

import android.content.Context
import io.realm.Realm
import io.realm.RealmConfiguration

object RealmManager {

  fun init(context: Context, key: ByteArray) {
    Realm.init(context)
    Realm.setDefaultConfiguration(RealmConfiguration.Builder()
        .deleteRealmIfMigrationNeeded()
        .encryptionKey(key)
        .build())
  }

  fun obtain(): Realm = Realm.getDefaultInstance()

}

3.

And lastly, below is the class responsible for encryption/decryption:

import android.content.Context
import android.security.KeyPairGeneratorSpec
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import com.hidglobal.tmt.app.BuildConfig
import timber.log.Timber
import java.math.BigInteger
import java.security.KeyPairGenerator
import java.security.KeyStore
import java.security.spec.AlgorithmParameterSpec
import java.util.*
import javax.inject.Inject
import javax.security.auth.x500.X500Principal

class RealmEncryptionHelper @Inject constructor() {

  companion object {
    private const val ALIAS = BuildConfig.APPLICATION_ID
    private const val ANDROID_KEYSTORE = "AndroidKeyStore"
    private const val PRINCIPAL_SUBJECT = "CN=Sample Name, O=Android Authority"
    private const val RSA_ALGORITHM = "RSA"
    private const val KEY_SIZE_BYTES = 64
  }

  fun loadKey(context: Context): ByteArray {
    val key = ByteArray(KEY_SIZE_BYTES)
    try {
      val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
      keyStore.load(null)
      if (!keyStore.containsAlias(ALIAS)) {
        generateKey(context)
      }
      val encoded = keyStore.getCertificate(ALIAS).encoded
      key.forEachIndexed { index, _ -> key[index] = encoded[index] }
    } catch (ex: Exception) {
      Timber.e(ex, "Unable to load Realm encryption key:")
    }
    return key
  }

  private fun generateKey(context: Context) {
    val spec: AlgorithmParameterSpec =
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
          KeyGenParameterSpec.Builder(
              ALIAS, KeyProperties.PURPOSE_SIGN)
              .setDigests(KeyProperties.DIGEST_SHA256)
              .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS)
              .build()
        } else {
          val start = Calendar.getInstance()
          val end = Calendar.getInstance()
          end.add(Calendar.YEAR, 1)
          KeyPairGeneratorSpec.Builder(context)
              .setAlias(ALIAS)
              .setSubject(X500Principal(PRINCIPAL_SUBJECT))
              .setSerialNumber(BigInteger.ONE)
              .setStartDate(start.time)
              .setEndDate(end.time)
              .build()
        }
    val generator = KeyPairGenerator.getInstance(RSA_ALGORITHM, ANDROID_KEYSTORE)
    generator.initialize(spec)
    generator.generateKeyPair()
  }

}

Version of Realm and tooling

Realm version(s): 5.9.1 (on the old version of previous application it is 4.3.1). I've tried also with 4.3.1

Android Studio version: 3.3.2

Android Build Tools version: 27.0.3

Gradle version: 3.1.3

Which Android version and device(s): Samsung S6 edge+ (Android 7.0, API 24) FAMOCO FX100 (Android 6.0, API 23)

cmelchior commented 5 years ago

Are you sure you are not generating a new encryption key after the app upgrade?

stennie commented 5 years ago

Closing this issue due to no response. Feel free to reopen if additional information can be provided.