Realm database file encryption swift - ios

I'm working on mobile application, using Realm as local database I have tried to encrypt the data inside the file but it's to much to handle.
I'm wondering if I can encrypt the Realm database file? if it possible how to do it?.
Thanks in advance.

Try this :
// Generate a random encryption key
var key = Data(count: 64)
_ = key.withUnsafeMutableBytes { bytes in
SecRandomCopyBytes(kSecRandomDefault, 64, bytes)
}
// Open the encrypted Realm file
let config = Realm.Configuration(encryptionKey: key)
do {
let realm = try Realm(configuration: config)
// Use the Realm as normal
let dogs = realm.objects(Dog.self).filter("name contains 'Fido'")
} 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)")
}

Related

Realm iOS: How to handle Client Reset

Basically, I want to handle a case where any device got SyncError with type ClientResetError then, want my device to re-login to realm again. but as per documentation, we have to closeRealmSafely before I login to realm again, but I am not sure how to close realm safely.
I am going through the doc (https://docs.realm.io/sync/using-synced-realms/errors#client-reset) to handle client reset error and found it's very confusing . I want help to understand about the following code.
First there is no method available to closeRealmsafely. Please help me understand how can I close the realm safely?
How can I backup and when I will use it? Should I skip the reset error because in documentation it's mentions if the client reset process is not manually initiated, it will instead automatically take place after the next time the app is launched, upon first accessing the SyncManager singleton. It is the app’s responsibility to persist the location of the backup copy if needed, so that the backup copy can be found later."
Below is the error handler sample code from the doc.
let syncError = error as! SyncError
switch syncError.code {
case .clientResetError:
if let (path, clientResetToken) = syncError.clientResetInfo() {
closeRealmSafely()
saveBackupRealmPath(path)
SyncSession.immediatelyHandleError(clientResetToken)
}
default:
// Handle other errors...
()
}
}```
Finally we figured out how to handle the client reset error. We have taken following steps To avoid the data loss incase user is offline and came online and got reset error.
Save the local realm to another directory
Invalidate and nil the realm
Initiate realm manual reset - Call SyncSession.immediatelyHandleError with clientResetToken passed and it will delete the existing realm from directory
Show client reset alert - This will intimate user to relaunch the app.
On next launch realm creates a fresh realm from ROS.
After new realm connects, restore the realm records (if any) from the old realm saved in backup directory above.
Delete the backup realm(old realm) from directory.
switch syncError.code {
case .clientResetError:
if let (path, clientResetToken) = syncError.clientResetInfo() {
// taking backup
backUpRealm(realm: yourLocalRealm)
// making realm nil and invalidating
yourLocalRealm?.invalidate()
yourLocalRealm = nil
//Initiate realm manual reset - Call `SyncSession.immediatelyHandleError` with `clientResetToken` passed and it will delete the existing realm from directory
SyncSession.immediatelyHandleError(clientResetToken)
// can show alert to user to relaunch the app
showAlertforAppRelaunch()
}
default:
// Handle other errors...
()
}
}```
The back up realm code look like this:
func backUpRealm(realm: Realm?) {
do {
try realm?.writeCopy(toFile: backupUrl)
} catch {
print("Error backing up data")
}
}
After doing this backup will be available at backup path. On next launch device will connect and download a fresh realm from ROS so after device connects restore the realm records from the backup realm saved in the backup path.
The restore merge backup code will look like this. place the below method when realm connects after relauch.The ```restoredRealm`` is fresh downloaded realm on launch
func restoreAndMergeFromBackup(restoredRealm: Realm?) {
let realmBackUpFilePath = isRealmBackupExits()
// check if backup exists or not
if realmBackUpFilePath.exists {
let config = Realm.Configuration(
fileURL: URL(fileURLWithPath: realmBackUpFilePath.path),
readOnly: true)
let realm = try? Realm(configuration: config)
guard let backupRealm = realm else { return }
//Get your realm Objects
let objects = backupRealm.objects(YourRealmObject.self)
try? restoredRealm?.safeWrite {
for object in objects {
// taking local changes to the downloaded realm if it has
restoredRealm?.create(YourRealmObject.self, value: object, update: .modified)
}
self.removeRealmFiles(path: realmBackUpFilePath.path)
}
} else {
debug("backup realm does not exists")
}
}
private func isRealmBackupExits() -> (exists: Bool, path: String) {
let documentsPath = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
let realmPathComponent = documentsPath.appendingPathComponent("your_backup.realm")
let filePath = realmPathComponent.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
return (true, filePath)
}
return (false, "")
}
private func removeRealmFiles(path: String) {
let realmURL = URL(fileURLWithPath: path)
let realmURLs = [
realmURL,
realmURL.appendingPathExtension("lock"),
realmURL.appendingPathExtension("realm"),
realmURL.appendingPathExtension("management")
]
for URL in realmURLs {
do {
try FileManager.default.removeItem(at: URL)
} catch {
debug("error while deleting realm urls")
}
}
}```
In our testing we have found that there is a backup made by realm automatically so we deleted it for safety purpose. the path argument you will get in the if let (path, clientResetToken) = syncError.clientResetInfo()
func removeAutoGeneratedRealmBackUp(path: String) {
do {
try FileManager.default.removeItem(at: URL(fileURLWithPath: path))
} catch {
debug("error while deleting realm backUp path \(path)")
}
}

How to bundle a realm file

I'm following the realm documentation on how to bundle a realm file. I've successfully loaded all necessary data into my encrypted file, but I can't seem to compact the file and move it elsewhere.
Code
// AppDelegate
fileprivate func compactRealm() {
if let realmPath = Realm.Configuration.defaultConfiguration.fileURL {
let destination = realmPath.deletingLastPathComponent().appendingPathComponent("compact.realm")
if FileManager.default.fileExists(atPath: realmPath.path) {
do {
// let encryption = Constants.key.data(using: String.Encoding.utf8)
try Realm().writeCopy(toFile: destination)
print("File normally compressed !")
} catch {
fatalError(error.localizedDescription)
}
} else {
print("Realm file does not exist")
// fatalError()
}
}
}
Result
Error Domain=io.realm Code=2 "Unable to open a realm at path '/var/mobile/Containers/Data/Application/B4D487F8-5AEC-4906-B989-7DB953095A35/Documents/default.realm': Not a Realm file." UserInfo={Error Code=2, NSFilePath=/var/mobile/Containers/Data/Application/B4D487F8-5AEC-4906-B989-7DB953095A35/Documents/default.realm, Underlying=Not a Realm file, NSLocalizedDescription=Unable to open a realm at path '/var/mobile/Containers/Data/Application/B4D487F8-5AEC-4906-B989-7DB953095A35/Documents/default.realm': Not a Realm file.}
I've checked : the realm file does exist !.
BTW, I've tried the same code with unencrypted file and it worked, so I don't know it wouldn't work with an encrypted realm file !
It appears that your line try Realm().writeCopy(toFile: destination) basically opens the default realm file, but without the key needed to decrypt it (I'm assuming you've already encrypted it here before attempting to write a compact copy).
Realm complains that the file can't be opened because it's not a Realm file (it's not, it's a scrambled version of it).
Open the Realm using the appropriate encryption key (try Realm(configuration: config) or similar) and then try writing a copy.
Sources
Realm Docs - Encryption
Realm Docs - Compacting Realms

Realm Encryption, Secure Enclave, and Keychain

Sorry to be yet another post about encryption, but I am struggling with setting up some truly strong encryption in my application. I currently have a basic setup for login that utilizes keychain and is based off of Tim Mitra's tutorial, which works wonderfully. However, I am uncomfortable about storing the account email / username in UserDefaults as it isn't particularly secure. Is there a better method that anyone can come up with? Additionally, I am working on utilizing Realm's built in encryption features, however I am unsure as to how I should properly store the key for said encryption given that it is of type Data. I also have heard that I should double encrypt the user's credentials using Secure Enclave and possibly utilize the same technique with Realm's key. Is there a guide that someone could point me to? How would you better optimize my code to be brutally secure? I have already set the application to check the device for Cydia and other signs of jailbreaking so as to avoid keychain data dumps and plan on checking any / all urls called too.
Here's my implementation of Keychain:
private func setupAccount()
{
let newAccountName = inputFields[0].text
let newPassword = inputFields[1].text
let hasLoginKey = UserDefaults.standard.bool(forKey: "hasSetup")
if !hasLoginKey {
UserDefaults.standard.setValue(inputFields[0].text, forKey: "username")
}
do {
// This is a new account, create a new keychain item with the account name.
let passwordItem = KeychainLIPassItem(service: KeychainConfiguration.serviceName,
account: newAccountName!,
accessGroup: KeychainConfiguration.accessGroup)
// Save the password for the new item.
try passwordItem.savePassword(newPassword!)
} catch {
fatalError("Error updating keychain - \(error)")
}
UserDefaults.standard.set(true, forKey: "hasSetup")
}
Here is what I currently have for Realm Encryption:
private func keyValue() -> Data
{
var key = Data(count: 64)
_ = key.withUnsafeMutableBytes { bytes in
SecRandomCopyBytes(kSecRandomDefault, 64, bytes)
}
return key
}
private func securitySettings() -> Realm.Configuration
{
let key = keyValue()
let config = Realm.Configuration(encryptionKey: key)
return config
}
private func setupObject()
{
do {
let realm = try Realm(configuration: securitySettings())
let profile = UserProfile()
profile.firstName = firstName
profile.lastName = lastName
profile.dateOfBirth = dateOfBirth
profile.gender = gender
try! realm.write {
realm.add(profile)
}
} catch let error as NSError {
fatalError("Error opening realm: \(error)")
}
}
Thank you so much!

How to create a pre bundled realm file and upload data to it?

I am new to Realm and I want to ship a pre bundled Realm file my app, however the realm documentation is unclear to me on how to actually create a .realm file and also upload the desired pre bundled data to it. I have not been able to find any solution via SO, Google, or Youtube. Step-by-step instructions would be very helpful.
We're still looking at ways to officially make generating pre-populated Realms more easy. It's definitely a desired feature for the Realm Browser, but due to the way that Realm files require a migration when changing the schema, it's somewhat tricky to incorporate into a UI. It's definitely something we want to improve down the line.
At the moment, the Browser has a converter in it that can perform minimal data import like CSV files.
The Realm documentation is saying that the easiest way for you to produce a pre-populated Realm specific for your apps needs is to build a separate macOS app whose sole role is to generate the Realm file, pre-populate the data and then expose the resulting Realm file so you can copy it to your app.
The operation to do this is just like any normal Realm interaction (try! Realm() is what causes the file to actually be initially created), except you manually interact with the Realm file on disk upon completion.
I'm working on an app for an iOS conference, and the schedule data for the event is going to be stored in a Realm that is pre-bundled with the app when it ships.
Since I thought creating an extra macOS app would be overkill for an iOS app, I instead used iOS unit tests that would generate the pre-bundled Realm from scratch every time it was executed.
class Tests: XCTestCase {
func testGenerateNewDefaultRealm() {
let sources = [conferences, sponsors, conferenceDays] as [Any]
XCTAssert(generateDefaultRealm(named: "MyConferenceRealm.realm", sources: sources))
}
}
extension Tests {
public func generateDefaultRealm(named name: String, sources: [Any]) -> Bool {
// Create and configure the Realm file we'll be writing to
let realm = generateRealm(named: name)
// Open a Realm write transaction
realm.beginWrite()
// Loop through each source and add it to Realm
for source in sources {
if let objectArray = source as? [Object] {
for object in objectArray {
realm.add(object)
}
}
else if let objectDictionary = source as? [String : Object] {
for (_, object) in objectDictionary {
realm.add(object)
}
}
}
// Commit the write transaction
do {
try realm.commitWrite()
}
catch let error {
print(error.localizedDescription)
return false
}
// Print the file location of the generated Realm
print("=====================================================================")
print(" ")
print("Successfully generated at")
print(realm.configuration.fileURL!.path)
print(" ")
print("=====================================================================")
return true
}
public func generateRealm(named name: String) -> Realm {
let exportPath = NSTemporaryDirectory()
let realmPath = exportPath.appending(name)
// Delete previous Realm file
if FileManager.default.fileExists(atPath: realmPath) {
try! FileManager.default.removeItem(atPath: realmPath)
}
// Create new Realm file at path
let objectTypes: [Object.Type] = [Conference.self, ConferenceDay.self, SessionBlock.self, Event.self, Presentation.self,
Session.self, Location.self, Speaker.self, Sponsor.self, Venue.self]
let configuration = Realm.Configuration(fileURL: URL(string: realmPath), objectTypes: objectTypes)
let realm = try! Realm(configuration: configuration)
return realm
}
}
Running this unit test will generate a new Realm file in the NSTemporaryDirectory() directory of the Mac, and then feed in a set of Array and Dictionary objects of un-persisted Realm Object instances that are created each time the test is run. The location of the final Realm is then printed in the console so I can grab it and move it to the app bundle.

Realm: Module 'Realm has no member named 'Configuration'

I am trying to enable encryption with realm using the latest official documentation:
https://realm.io/docs/swift/latest/#encryption
This is the code from the documentation:
import Foundation
import Realm
class TestRealm:NSObject {
func test() {
// Generate a random encryption key
let key = NSMutableData(length: 64)!
SecRandomCopyBytes(kSecRandomDefault, key.length,
UnsafeMutablePointer<UInt8>(key.mutableBytes))
// Open the encrypted Realm file
let config = Realm.Configuration(encryptionKey: key)
do {
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)")
}
}
}
I am receiving a compilation error on this line:
let config = Realm.Configuration(encryptionKey: key)
Module 'Realm has no member named 'Configuration'
I am using Realm 1.0.2
You need import RealmSwift, not import Realm. import Realm gives you the Objective-C API.

Resources