13

I have an iOS app that use a non-encrypted realm database.

Now I would like to apply encryption on that database.

Can I just set an encryption key using:

Realm.setEncryptionKey(key, forPath: Realm.defaultPath)

and then realm will encrypt the existing database?

Or do I need to create a new realm database file with encryption and then move the data in the existing database to the new encrypted database?

Pelle Stenild Coltau
  • 1,268
  • 10
  • 15

3 Answers3

13

You'll have to create an encrypted copy of the unencrypted Realm file, which you can do by using Realm().writeCopyToPath(_:encryptionKey:) and then you can use the encrypted file at the new location.

jpsim
  • 14,329
  • 6
  • 51
  • 68
1

You have to do something like below

  1. Make function with return value func encryptedrealm() -> Realm

    func encryptedrealm() {

    // Get the encryptionKey
    var realmKey = getencryptionKey()
    

    // Check if the user has the unencrypted Realm
    let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
    let fileManager = FileManager.default
    let unencryptedRealmPath = "\(documentDirectory)/default.realm"
    let encryptedPath = "\(documentDirectory)/default_new.realm"
    let isUnencryptedRealmExsist = fileManager.fileExists(atPath: unencryptedRealmPath)
    let isEncryptedRealmExsist = fileManager.fileExists(atPath: encryptedPath)


    if isUnencryptedRealmExsist && !isEncryptedRealmExsist {
        let unencryptedRealm = try! Realm(configuration: Realm.Configuration(schemaVersion: 7))
        // if the user has unencrypted Realm write a copy to new path
        try? unencryptedRealm.writeCopy(toFile: URL(fileURLWithPath: encryptedPath), encryptionKey: realmKey)
    }

    // read from the new encrypted Realm path
    let configuration = Realm.Configuration(fileURL: URL(fileURLWithPath: encryptedPath), encryptionKey: realmKey, schemaVersion: 7, migrationBlock: { migration, oldSchemaVersion in })

    return try! Realm(configuration: configuration)

}
  1. Create encryption key or fetch exists key from keychain, make function `func getencryptionKey() -> Data

func getencryptionKey() {

let keychainIdentifier = "io.Realm.EncryptionKey"

    let keychainIdentifierData = keychainIdentifier.data(using: String.Encoding.utf8, allowLossyConversion: false)!
   
    var query: [NSString: AnyObject] = [
        kSecClass: kSecClassKey,
        kSecAttrApplicationTag: keychainIdentifierData as AnyObject,
        kSecAttrKeySizeInBits: 512 as AnyObject,
        kSecReturnData: true as AnyObject
    ]
  
    var dataTypeRef: AnyObject?
    var status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) }
    if status == errSecSuccess {
        // swiftlint:disable:next force_cast
        return dataTypeRef as! Data
    }
    // No pre-existing key from this application, so generate a new one
    // Generate a random encryption key
    var key = Data(count: 64)
    key.withUnsafeMutableBytes({ (pointer: UnsafeMutableRawBufferPointer) in
        let result = SecRandomCopyBytes(kSecRandomDefault, 64, pointer.baseAddress!)
        assert(result == 0, "Failed to get random bytes")
    })
   
    query = [
        kSecClass: kSecClassKey,
        kSecAttrApplicationTag: keychainIdentifierData as AnyObject,
        kSecAttrKeySizeInBits: 512 as AnyObject,
        kSecValueData: key as AnyObject
    ]
    status = SecItemAdd(query as CFDictionary, nil)
    assert(status == errSecSuccess, "Failed to insert the new key in the keychain")
    return key


 }
  1. Now you can use realm like below

    let realm = encryptedrealm()

0

In my projects I use this complete method for realm encryption. It supports encryption on existing and not encrypted realm file.

 static func setupEncryption() {
    let isRealmEncryptedKey = "isRealmEncryptedKey"
    let keychainKey = "realmEncryptionKey"
    
    var config = Realm.Configuration.defaultConfiguration
    let isRealmEncrypted = UserDefaults.standard.bool(forKey: isRealmEncryptedKey)
    
    if isRealmEncrypted, let userEncryptionKey = KeychainSwift().getData(key: keychainKey) {
        // Fetch and apply existing encryption key
        config.encryptionKey = Data(userEncryptionKey)
    } else if let keyData = NSMutableData(length: 64) {
        // Create and apply encryption key
        let _ = SecRandomCopyBytes(kSecRandomDefault, keyData.length, keyData.mutableBytes)
        let encryptionKey = Data(keyData)
       
        // If realm file already exists, it should be replaced by new encrypted realm file. Actual for cases when user updates app to version with encryption feature
        if let currentRealmUrl =  FileUtils.getDocumentDirectory()?.appendingPathComponent("default.realm"), var encryptedRealmUrl = FileUtils.getDocumentDirectory()?.appendingPathComponent("encrypted.realm"), FileManager.default.fileExists(atPath: currentRealmUrl.path) {
            
            do {
                try Realm.instance?.writeCopy(toFile: encryptedRealmUrl, encryptionKey: encryptionKey)
            } catch {
                assert(false, "Creation of realm encrypted copy failed. Realm is invalid")
            }
            
            do {
                try FileManager.default.removeItem(at: currentRealmUrl)
                var resourceValues = URLResourceValues()
                resourceValues.name = "default.realm"
                try encryptedRealmUrl.setResourceValues(resourceValues)
            } catch {
                assert(false, "Realm encryption failed. Realm is invalid")
            }
        }
       
        UserDefaults.standard.set(true, forKey: isRealmEncryptedKey)
        KeychainSwift().set(encryptionKey, forKey: keychainKey)
        config.encryptionKey = encryptionKey
    }
    
    Realm.Configuration.defaultConfiguration = config
}