加密 Realm — Swift SDK
概述
您可以通过在打开 realm 时提供 64 字节加密密钥,使用 AES-256 + SHA-2 加密磁盘上的 realm 文件。
Realm 使用标准 AES-256 加密 以透明方式加密和解密数据 使用给定256 位加密密钥的前512 位。 Realm 使用 位加密密钥的其他256 位,通过512 基于哈希的消息身份验证代码 (HMAC) 来验证完整性。
警告
不要将弱加密哈希值用于 Realm 加密密钥。为了获得最佳安全性,我们建议生成随机加密密钥,而不是派生加密密钥。
注意
无法加密现有的未加密 Realm
您必须在首次打开 Realm 时对其进行加密。如果您尝试使用包含加密密钥的配置打开现有的未加密 Realm,Realm 会引发错误。
注意事项
以下是加密 Realm 时需要考虑的主要影响。
存储和重用密钥
每次打开加密 Realm 时都必须传递相同的加密密钥。如果您不提供密钥或为加密 Realm 指定了错误的密钥,Realm SDK 会引发错误。
应用程序应将加密密钥存储在密钥链中,以使其他应用无法读取该密钥。
性能影响
加密 Realm 的读取和写入速度可能比未加密 Realm 慢 10%。
加密和 Atlas Device Sync
您可以对同步 Realm 进行加密。
Realm 仅对设备上的数据进行加密,并将未加密的数据存储在 Atlas 数据源中。任何有权访问 Atlas 数据源的用户都可以读取数据,但以下规则仍然适用:
用户必须拥有正确的读取权限才能读取同步数据。
Atlas 中存储的数据始终在卷(磁盘)级别进行加密。
客户端和服务器之间的传输始终是完全加密的。
您还可以启用客户密钥管理,使用云提供商的密钥(例如AWS KMS、Azure Key Vault、Google Cloud KMS)对存储的 Atlas 数据进行加密。
如果您需要为应用程序的每个用户提供唯一的密钥,则可以使用 OAuth 提供程序或使用一个Realm身份验证提供程序和身份验证trigger来创建 64 位密钥并将该密钥存储在用户对象中。
从多个进程访问加密 Realm
在版本 10.38.0 中更改。
从 Realm Swift SDK 版本 10.38.0 开始,Realm 支持在多个进程中打开相同的加密 realm。
如果您的应用程序使用 Realm Swift SDK 版本 10.37.2 或更早版本,则尝试从多个进程打开加密领域会引发以下错误:
Encrypted interprocess sharing is currently unsupported.
使用早期 SDK 版本的应用程序有两个选项可以在多个进程中处理 Realm:
使用未加密的 Realm。
将要加密的数据存储为 Realm 对象上的
NSData
属性。然后,您就可以加密和解密单个字段。
一种可能的加密和解密字段的工具是 Apple 的 CryptoKit 框架。您可以使用 Swift Crypto 以通过 CryptoKit 简化应用程序开发。
例子
以下代码演示如何生成加密密钥和打开加密 Realm:
// Generate a random encryption key NSMutableData *key = [NSMutableData dataWithLength:64]; (void)SecRandomCopyBytes(kSecRandomDefault, key.length, (uint8_t *)key.mutableBytes); // Open the encrypted Realm file RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.encryptionKey = key; NSError *error = nil; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error]; if (!realm) { // If the encryption key is wrong, `error` will say that it's an invalid database NSLog(@"Error opening realm: %@", error); } else { // Use the realm as normal... }
// Generate a random encryption key var key = Data(count: 64) _ = key.withUnsafeMutableBytes { (pointer: UnsafeMutableRawBufferPointer) in SecRandomCopyBytes(kSecRandomDefault, 64, pointer.baseAddress!) } // Configure for an encrypted realm var config = Realm.Configuration(encryptionKey: key) do { // Open the encrypted realm let realm = try Realm(configuration: config) // ... use the realm as normal ... } catch let error as NSError { // If the encryption key is wrong, `error` will say that it's an invalid database fatalError("Error opening realm: \(error.localizedDescription)") }
以下 Swift 示例演示如何从钥匙串中存储和检索生成的密钥:
// Retrieve the existing encryption key for the app if it exists or create a new one func getKey() -> Data { // Identifier for our keychain entry - should be unique for your application let keychainIdentifier = "io.Realm.EncryptionExampleKey" 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 // See also: http://stackoverflow.com/questions/24145838/querying-ios-keychain-using-swift/27721328#27721328 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") }) // Store the key in the keychain 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 } // ... // Use the getKey() function to get the stored encryption key or create a new one var config = Realm.Configuration(encryptionKey: getKey()) do { // Open the realm with the configuration let realm = try Realm(configuration: config) // Use the realm as normal } catch let error as NSError { // If the encryption key is wrong, `error` will say that it's an invalid database fatalError("Error opening realm: \(error)") }