Realm数据库加密和迁移问题小结

2,502 阅读2分钟

声明: 转载注明本人出处, 请在方便的情况下尽量告知.

尊重原创, 共同进步.


前提

本人使用的是RealmSwift 4.1.2版本, 因为项目需求, 从原来的无加密(俗称"Luo Ben"), 修改为需要对其进行加密. 这里涉及到两个点.

  1. 数据库加密
  2. 数据库迁移

数据库加密

基本思路: 填入加密Key, 对整个数据库进行加密.

Realm.Configuration.encryptionKey = ***

加密Key, 可考虑使用常(写)量(死), 或者生成随机值, 再使用KVO/KeyChain等保存.

这里使用的是KeyChain!

代码参考(拷贝自)RealmSwift的官方Demo.

    // Identifier for our keychain entry - should be unique for your application
    let keychainIdentifier = "com.abc.ABC.KeyName"
    let keychainIdentifierData = keychainIdentifier.data(using: String.Encoding.utf8,
        allowLossyConversion: false)!

    // First check in the keychain for an existing key
    var query: [NSString: AnyObject] = [
        kSecClass: kSecClassKey,
        kSecAttrApplicationTag: keychainIdentifierData as AnyObject,
        kSecAttrKeySizeInBits: 512 as AnyObject,
        kSecReturnData: true as AnyObject
    ]

    // To avoid Swift optimization bug, should use withUnsafeMutablePointer() function to retrieve the keychain item
    var dataTypeRef: AnyObject?
    var status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary,
        UnsafeMutablePointer($0)) }
    if status == errSecSuccess {
        return dataTypeRef as? Data
    }

    // No pre-existing key from this application, so generate a new one
    let keyData = NSMutableData(length: 64)!
    //随机生成一个Key, 但需要做保存
    let result = SecRandomCopyBytes(kSecRandomDefault, 64, keyData.mutableBytes.bindMemory(to: UInt8.self, capacity: 64))
    assert(result == 0, "Failed to get random bytes")

    // Store the key in the keychain
    query = [
        kSecClass: kSecClassKey,
        kSecAttrApplicationTag: keychainIdentifierData as AnyObject,
        kSecAttrKeySizeInBits: 512 as AnyObject,
        kSecValueData: keyData
    ]

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

获取Realm实例的时候也使用同一个key进行解密. 因为本人将Realm配置写入了其默认配置中, 所以直接读取即可.

    let configuration = Realm.Configuration.defaultConfiguration
    let realm = try Realm(configuration: configuration)

数据库迁移

**基本思路: ** 尝试打开使用加密Key, 打开无加密的Realm文件. 成功, 则返回实例; 失败, 则进行文件(加密)复制, 覆盖文件后重新打开实例. 伪代码如下:

    do {
        let realm = try Realm(configuration: configuration)
        return realm
    } catch { //加密Key更新, 导致异常, 进行数据库迁移
        var realm: Realm?
        if configuration.schemaVersion >= 666 {
            let key = configuration.encryptionKey
            var bkConfig = configuration
            bkConfig.encryptionKey = nil
            //其它可能的修改
            
            var noException = false
            autoreleasepool { //重要! 确保Realm实例回收
                do {
                    let bkRealm = try Realm(configuration: bkConfig)
                    guard let bkUrl = configuration.fileURL else { return }
                    
                    let migrateFile = bkUrl.absoluteString + "_mig"
                    guard let migrateURL = URL(string: migrateFile) else { return }
                    
                    do {
                        try bkRealm.writeCopy(toFile: migrateURL, encryptionKey: key)
                        let fileManager:FileManager = FileManager.default
                        //删除旧文件
                        //...
                        //移动新文件到旧文件路径
                        //...
                        
                        noException = true
                    } catch {
                        OdmLogger.e(message: "Realm数据库拷贝失败!")
                    }
                } catch {
                    OdmLogger.f(message: "Realm数据库发生严重严重的错误!")
                }
            }

            if noException {
                //Realm数据库必须在上一个实例销毁时, 才能再次使用同一个路径打开另外一个实例
                realm = try Realm(configuration: configuration)
            }
        }

        return realm
    }

结论

该操作方式也适用于切换修改Key的Realm数据库升级迁移需求, 具体就不一一赘述了.