realm-swift: Seeing "Realm file decryption failed" errors

Background Our iOS app is live with over one hundred thousand users. Of those, we have an issue happening with approximately 80 users. We’re unable to reproduce this locally, but this is what we’re able to piece together from Crashlytics.

Details We store sensitive customer data in an encrypted Realm. To accomplish this, we have extended Realm with a static function that returns our encrypted Realm, or throws an error if it fails.

For 99% of users, this works fine. For less than 1%, we’re seeing the “Realm file decryption failed” error. From investigating, it seems that this error is caused by trying to access a Realm with either the wrong key, or by trying to decrypt an unencrypted Realm.

Right now, with how we’re generating the key, I’m inclined to think that that is not the issue.

Code Sample We have a Realm extension with the following:

static func encryptedRealm() throws -> Realm {
    // Fetches key from Keychain if one exists, otherwise creates new Data key, stores in Keychain, returns it here.
        let key: Data = getEncryptionKey() 
        
        // Create default config
        let config = Realm.Configuration(encryptionKey: key)
        
        // Return Realm for config
        do {
            let realm = try Realm(configuration: config)
            return realm
        } catch let error as NSError {
            throw error
        }
  }

The first place Realm is hit in our app is via our User Settings manager. Simple version:

class SettingsManager {
    /// Shared instance
    static let shared = SettingsManager()
    
    /// Realm instance
    var realm: Realm {
        do {
            return try Realm.encryptedRealm()
        } catch let error as NSError {
            // Some logging to Crashlytics happening here
            fatalError()
        }
    }

   // Other methods/properties here as convenience accesses to user data
}

Expected results While we accounted for the fact that something could go wrong, I was expecting to see errors pertaining to file I/O, disk space, etc.

Actual results For less than 1% of our users, we’re seeing “Realm file decryption failed”. As all hits to Realm are happening through the Realm.encryptedRealm() function, I don’t know how a non-encrypted Realm could be created. So I’m at a loss as to what might be going on.

The Crashlytics trace has our realm property getting above, followed by:

libswiftCore.dylib
__hidden#23281_ line 134
specialized _assertionFailure(StaticString, String, file : StaticString, line : UInt, flags : UInt32) -> Never

Looking up errors for that has shown me potentially issues with Realm, background threads, and the need to use autoreleasepool. But the error I’m catching before the fatalError call seems to imply otherwise.

Any guidance would be welcome at this point. Thanks.

Version of Realm 2.10.2 (Unable to upgrade at this time)

Xcode version: 9.2

iOS/OSX version: Bug has been reported on iOS 10 and 11

Dependency manager + version: CocoaPods 1.4.0

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 6
  • Comments: 17 (2 by maintainers)

Most upvoted comments

We just realized what was the problem and I thought it might be helpful for you to know what we did in case it helps to fix your issue as well.

Our scenario is the following one:

  • We do have multiple environments (Debug, QA, Production)
  • We wanted to use a specific encription key for Debug mode and a runtime random generated key for QA and Production environments.
  • We are saving encription key to Keychain once it is generated the first time. We always recover it in later executions.
  • We used KeychainSwift library to make things easier when interacting with Keychain (https://github.com/evgenyneu/keychain-swift).
  • We do have Apple CarPlay support and device is automatically locked once the user connects his/her device to the infotainment system.

We were experiencing a random issue where the encription key failed to open the database. There was no usage pattern to reproduce it and it happened also in Production for some users.

What we did is:

  • Ensuring we had different keychain keys for each of our environments (e.g. com.ourcompany.ourappname.databaseEncriptionKeyDebug, com.ourcompany.ourappname.databaseEncriptionKeyQa, com.ourcompany.ourappname.databaseEncriptionKeyProd). Some users were exchanging beta and production releases and a wrong key was being used.
  • Ensuring the database encription key was being saved with permissions to access it while the device was locked (see https://github.com/evgenyneu/keychain-swift/issues/78 for more information). When they were connected to Apple CarPlay, Keychain item was not accessible at all if they didn’t open the app in their phone screen first.
  • Using barriers (GCD) to perform only a one read/write operation at a time.

After all of those changes, there were still a few users having problems with database access. We realized there was a situation where the user was using iCloud to make backups of their phone but wasn’t using iCloud Keychain. He/she changed or made a factory reset to his/her phone and restored the iCloud backup. Once the app was being started, Keychain returned nil for com.ourcompany.ourappname.databaseEncriptionKeyProd key and database failed to open (obviously). We added a specific control for this situation and created an empty database with a brand new encription key saved to the new Keychain.

Hope it helps.

One thing I notice about your getKey() function is that it assumes that the only two possible results are “successfully read key” and “key not found”. Perhaps in some rare situations the key is in the keychain but not readable (due to something like the device being locked?), in which case your app will instead generate a new key and then fail to open the existing Realm.

Hey @JoshHrach @Spenders @corteggo @bmunkholm @jguerinet I think the issue might be about waking from a suspended state and Realm not starting fast enough to reply to the request to open the database file. We turned off all iOS background processing in the XCode project settings and it solved our main issue. We also think we’ve found a situation when we call the keychain for credentials for an encrypted database and it’s returning the wrong credentials (maybe older ones), so we’re now in the process of seeing if we can get multiple credentials and then cycle through each one to ensure we find the one that’s valid. I hope this is helpful to you!

@nwainwright Thanks for the update. I don’t believe this is the case for me, as this is happening on the main thread upon opening the completely-terminated app. I also don’t know how the wrong credentials could have affected this, as we have only ever used one. Regardless, thanks for the theories and good luck!

@tgoyne This was a case I had originally considered as well, except that in that situation this would crash:

status = SecItemAdd(query as CFDictionary, nil)
assert(status == errSecSuccess, "Failed to insert the new key in the keychain")

Since we’re asking to add and not to update, if the key already existed in any capacity the result would be errSecDuplicateItem and crash the app in itself (I tested this). And if for some reason it came back as errSecSuccess, the original key would have been lost since it seemingly no longer exists, and there wouldn’t be much I could anyway. I don’t believe this to be the case as I don’t see where the key would be deleted in the first place, and there are clearly other people facing the same bug. Thank you for your suggestion though!

@Spenders No luck on our end. I ended up doing an early check of the Realm (within the AppDelegate) and am deleting the database if it hits this. It’s cut down on crashes in our app, but it hasn’t addressed the ultimate problem.