Migrate from unencrypted realm to encrypted realm - ios

I'm trying to migrate from unencrypted realm to encrypted but I don't know how and where to use Realm().writeCopy(toFile: url, encryptionKey: key).
or even if there is another way to do it.
Thank you.

I found a way to do that, you can find it below:
private static var realm: Realm! {
// Get the encryptionKey
var realmKey = Keychain.realmKey
if realmKey == nil {
var key = Data(count: 64)
key.withUnsafeMutableBytes { (bytes) -> Void in
_ = SecRandomCopyBytes(kSecRandomDefault, 64, bytes)
}
realmKey = key
Keychain.realmKey = realmKey
}
// 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)
}

According to #Abedalkareem Omreyh's answer
You can also Retrieve the existing encryption key for the app if it exists or create a new one.
func getencryptionKey() -> 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)")
}
you can use this encryption Key for above code and don't need to check nil value of key.
// Get the encryptionKey
var realmKey = getencryptionKey()
Reference link Encrypt a Realm

Related

Certificate public key missing some bytes when parsed using iOS vs Android

I have a certificate "cert.p12" that I use to sign some data to send it to s SAS server
I read the certificate this way
let data = try Data.init(contentsOf: _certURL)
let password = "somepassword"
let options = [ kSecImportExportPassphrase as String: password ]
var rawItems: CFArray?
let status = SecPKCS12Import(data as CFData,
options as CFDictionary,
&rawItems)
guard status == errSecSuccess else {
print("Error[36]")
return
}
let items = rawItems! as! Array<Dictionary<String, Any>>
let firstItem = items[0]
let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity?
let trust = firstItem[kSecImportItemTrust as String] as! SecTrust
let publicKey = SecTrustCopyKey(trust)
let publicKeyData = SecKeyCopyExternalRepresentation(publicKey!, nil)
let publicKeyString = (publicKeyData! as Data).base64EncodedString()
I get the public key like this
"MInsdfknsiufiwefwef ...... etc"
While in android I get
"[Ghhsdfwe89fwenfnwekfjwef]MInsdfknsiufiwefwef ......etc"
So the key is same except for the first like 40 bytes or so
See the image below where I use text comparison tool
The key at the bottom is the correct one accepted by the SAS server
What could be going wrong or missing in iOS

How to extract certificate and private key PEM strings from PKCS#12 file using swift

I have a use case where the user gives a PKCS#12 file and its password as input, and I need both the certificate's and the private key's PEM strings.
I have managed to create a SecIdentity using the p12, and its documentation says:
A SecIdentity object contains a SecKey object and an associated SecCertificate object.
So, I believe I am in the right path, but I couldn't find a way of extracting the SecKey and the SecCertificate from this SecIdentity.
I have also not found a way to get a PEM string from a SecKey or SecCertificate, but that would only be the last step.
This is the code I've used to create the identity:
let key: NSString = kSecImportExportPassphrase as NSString
let options: NSDictionary = [key : p12Password]
var rawItems: CFArray?
let p12Data = try! Data(contentsOf: p12FileUrl)
let data = p12Data! as NSData
let cfdata = CFDataCreate(kCFAllocatorDefault, data.bytes.assumingMemoryBound(to: UInt8.self), data.length)!
let result: OSStatus = SecPKCS12Import(cfdata, options, &rawItems)
if result == errSecSuccess {
let items = rawItems! as! [[String: Any]]
let firstItem = items[0]
let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity
}
-- UPDATE --
I made some more progress, and managed to extract the certificate in a DER format (which I haven't tried to convert to PEM so far - I believe it should be easy), but I still have no idea how to get the private key.
if result == errSecSuccess {
let items = rawItems! as! [[String: Any]]
let firstItem = items[0]
let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity
var cert: SecCertificate?
SecIdentityCopyCertificate(identity, &cert)
var certDer = SecCertificateCopyData(cert!) //DER format
var key: SecKey?
SecIdentityCopyPrivateKey(identity, &key)
let keyDict = SecKeyCopyAttributes(key!) //Not sure what we can find here
}

SecKeyCopyKeyExchangeResult does not work as nodejs ecdh in iOS 13

I have an application where I generate shared secret between iOS / android / nodejs using ECDH over secp521r1 curve. To ensure that it works I wrote a test that uses data generated with nodejs and that worked until iOS 13.
The source code of the test was:
let publicKey = "BABBvZ56c4bj1Zo73LIt/bBVa3jvGTA1fceoOG/M9TeXHx5ffCggRteEVS+bwrgQWPOwJPHhevNenaVn32ZnhztS0QFBqKGZTF1pKNSvuj+PDKQ625TauNroq+LQdeS+Pn6GVHL0iW5pp84NZ06L97VZ9HYm+g2lMnlUFV8hco2CmwBqHQ=="
let privateKey = "AXn994UN59QCEqmCmXmmNZ3hVZPlMwzTIeBupJGG4CqDWfWLuCTui7qiBfQtCFcQ1ks4NNB/tHEZUJ+bB97+pkJ3"
let otherBase64 = "BAAzWyzdh2e+ZNUCFt4oDADURb8+m9WA7gbWtTo57ZP3U23VuvMnRHf+12GpTSV8A5pt+vZfaR2cT02P+LPRc/kGzgAT2IYIgDz/cKbzMi520ZLa0GYk1xzCuNqFhdBZmrB5w0ymsPLdJzIG1QZ3xu7OufEipm5D41abphLLnbH+OyTX6w=="
let expectedShared = "AQkTOOHPcvlXufR2dm1FHaIJRlTgmxTJMI+h0kJ+nMVNopIP+opSqUNmflsgnJzT8JTodd/eehaaq5vvYdDVciIQ"
// iOS secKey is reconstructed by concatenating public and private key
let otherDataKey = Data.init(base64Encoded: otherBase64)!
var concatenatedKey = Data.init(base64Encoded: publicKey)!
concatenatedKey.append(Data.init(base64Encoded: privateKey)!)
// generate private key
var attributes: [String:Any] =
[
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecAttrKeySizeInBits as String: 521,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
]
var error: Unmanaged<CFError>?
guard let secKey = SecKeyCreateWithData(concatenatedKey as CFData, attributes as CFDictionary, &error) else {
XCTAssertTrue(false)
return
}
// generate other public key
attributes[kSecAttrKeyClass as String] = kSecAttrKeyClassPublic
guard let otherKey = SecKeyCreateWithData(otherDataKey as CFData, attributes as CFDictionary, nil) else {
XCTAssertTrue(false)
return
}
// generate shared secret
let exchangeOptions: [String: Any] = [:]
guard let shared = SecKeyCopyKeyExchangeResult(secKey, SecKeyAlgorithm.ecdhKeyExchangeStandardX963SHA256, otherKey, exchangeOptions as CFDictionary, &error) else {
XCTAssertTrue(false)
return
}
// generate shared secret
XCTAssertEqual((shared as Data).base64EncodedString(), expectedShared);
With iOS 13 I was forced to modify the content of my exchangeOptions dictionary as discussed here (SecKeyCopyKeyExchangeResult() function return an error, "kSecKeyKeyExchangeParameterRequestedSize is missing")
let exchangeOptions: [String: Any] = [SecKeyKeyExchangeParameter.requestedSize.rawValue as String: 66]
The problem is that with this option, the result of SecKeyCopyKeyExchangeResult does not match anymorewith nodejs one (which is also true on iOS 12)
I finally found a solution... In iOS <= 12, leaving exchange parameters empty when trying to use ecdhKeyExchangeStandardX963SHA256 algorithm was falling back to using SecKeyAlgorithm.ecdhKeyExchangeCofactor.
Therefore the fix to reproduce previous behavior is to modify the SecKeyCopyKeyExchangeResult with
// generate shared secret
let exchangeOptions: [String: Any] = [:]
guard let shared = SecKeyCopyKeyExchangeResult(secKey, SecKeyAlgorithm.ecdhKeyExchangeCofactor, otherKey, exchangeOptions as CFDictionary, &error) else {
XCTAssertTrue(false)
return
}
This works at least for iOS 10 to 13

CryptoSwift + CryptoJS Swift

I really need some help here with decrypting server response encrypted with CryptoJS.
I use CryptoSwift to decrypt response.
Here is a sample of server response data I get.
{\"ct\":\"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\",\"iv\":\"d6d6bd8ee407bc25a7b23d8d36b7bce9\",\"s\":\"b8e72892c801c87c\"}"}
Simple string parsing clears the encrypted string and gives me the following data:
let iv = "d6d6bd8ee407bc25a7b23d8d36b7bce9"
let salt = "b8e72892c801c87c"
and a key formend with other data
let key = "8aa1ec1e6948b481d1ee450c94ffb2edc774877325df4d05aca2e5827497ed33"
Here is the peace of code I use to decrypt response:
// transforming key to [UInt8]
let keyChars = Array(key.characters)
let keyBytes = 0.stride(to: keyChars.count, by: 2).map {
UInt8(String(keyChars[$0 ..< $0+2]), radix: 16) ?? 0
}
// transforming iv to [UInt8]
let ivChars = Array(iv.characters)
let ivBytes = 0.stride(to: ivChars.count, by: 2).map {
UInt8(String(ivChars[$0 ..< $0+2]), radix: 16) ?? 0
}
// transforming encryptedData to [UInt8]
let messageData = encrypted.dataFromHexadecimalString()
let byteArray = messageData!.arrayOfBytes()
do {
let decryptedBytes: [UInt8] = try AES(key: keyBytes, iv: ivBytes, blockMode: .CFB).decrypt(byteArray)
let data = NSData.withBytes(decryptedBytes)
let decrypted = data.base64EncodedStringWithOptions([])
let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
print("decrypted = \(json)")
} catch {
print("error = \(error)")
}
What ever I do I get either error = Decrypt or base64 string that does not decode to a JSON as it suppose to.
P.S.: I did try CryptoJS.swift but the result was "undefined"
UPD
Sample project
This is how data encrypted on back end:
CryptoJS.AES.encrypt(JSON.stringify(options.params), key, { format: JsonFormatter }).toString()
This is how data decrypted on back end:
JSON.parse(CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(raw, key, { format: JsonFormatter })))
I tried to to something similar in my sample project but for some reason it did not work for me.
UPD2
Update from back end
// Nodejs import
var node_cryptojs = require('node-cryptojs-aes');
var CryptoJS = node_cryptojs.CryptoJS;
var JsonFormatter = node_cryptojs.JsonFormatter;
// Data to encrypt and encryption key
var data = {'hello':'world'};
var key = '8aa1ec1e6948b481d1ee450c94ffb2edc774877325df4d05aca2e5827497ed33';
// Encryption of the data
var encrypted = CryptoJS.AES.encry
var decrypted = JSON.parse(CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(encrypted, key, { format: JsonFormatter })))
UPD3
No, the name is similar but the API is different. We used npmjs.com/package/node-cryptojs-aes on the server-side (encryption+ decryption) and code.google.com/archive/p/crypto-js client-side on our site (also encryption+decryption)
Sample of encrypted data
{"_id":"5687ad129b65920a00b56a9b","type":"user","created":"2016-01-02T10:57:22.851Z","uuid":"d9df3412cee97ec1d0a8c547f73e4bb6","secret":"307a14f6ffc667c7941e6263edca4149","profile":{"lastname":"Mmm","gender":"Male","firstname":"Mmm","email":"mmm#mmm.mmm","dob":"1993-10-31T00:00:00.000+0200"},"avatar":{"large":"https://graph.facebook.com/v2.4/1122734811071660/picture?width=120&height=120"},"location":{"country":{"filename":"greece","code":"GR","continent":"Europe","name":"Greece"},"state":{"id":"Aitolia
kai Akarnania","country":"GR","name":"Aitolia kai
Akarnania"}},"auth":{"facebook":{"userID":"1122734811071660"}},"notifications":{"new_window":{"sms":false,"email":true,"push":false},"new_live":{"sms":false,"email":true,"push":false},"new_premium":{"sms":true,"email":true,"push":false},"reminder":{"sms":true,"email":true,"push":false},"new_arcade":{"sms":true,"email":true,"push":false},"ranking":{"sms":false,"email":true,"push":false}},"metas":{},"stats":{"game":{"time":{"total":1084452,"maze":{"mean":180436,"stdev":423,"min":180013,"max":180859,"sum":360872},"wordsearch":{"mean":111639.5,"stdev":68379.5,"min":43260,"max":180019,"sum":223279},"trivia":{"mean":22410,"stdev":0,"min":22410,"max":22410,"sum":22410},"brokenword":{"mean":40399,"stdev":0,"min":40399,"max":40399,"sum":40399},"imagelabel":{"mean":38349.5,"stdev":22808.5,"min":15541,"max":61158,"sum":76699},"scramble":{"mean":180174,"stdev":0,"min":180174,"max":180174,"sum":180174},"sort":{"mean":180619,"stdev":0,"min":180619,"max":180619,"sum":180619}},"score":{"total":4500,"maze":{"mean":null,"stdev":null,"min":null,"max":null,"sum":0},"wordsearch":{"mean":1000,"stdev":0,"min":1000,"max":1000,"sum":1000},"trivia":{"mean":800,"stdev":0,"min":800,"max":800,"sum":800},"brokenword":{"mean":800,"stdev":0,"min":800,"max":800,"sum":800},"imagelabel":{"mean":950,"stdev":50,"min":900,"max":1000,"sum":1900},"scramble":{"mean":null,"stdev":null,"min":null,"max":null,"sum":0},"sort":{"mean":null,"stdev":null,"min":null,"max":null,"sum":0}}},"positions":{"position":{"avg":0}},"played":{"window":1,"total":232,"live":120,"arcade":101,"live-duplicate":10}},"credits":487,"utm":"false","perms":{"root":true},"undefined":null,"value":{"credits":520,"usd":52,"bought":3},"premium":true}

How do I get a plist as a Dictionary in Swift?

I am playing around with Apple's new Swift programming language and have some problems...
Currently I'm trying to read a plist file, in Objective-C I would do the following to get the content as a NSDictionary:
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"Config" ofType:#"plist"];
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:filePath];
How do I get a plist as a Dictionary in Swift?
I assume I can get the path to the plist with:
let path = NSBundle.mainBundle().pathForResource("Config", ofType: "plist")
When this works (If it's correct?): How do I get the content as a Dictionary?
Also a more general question:
Is it OK to use the default NS* classes? I think so...or am I missing something? As far as I know the default framework NS* classes are still valid and alright to use?
You can still use NSDictionaries in Swift:
For Swift 4
var nsDictionary: NSDictionary?
if let path = Bundle.main.path(forResource: "Config", ofType: "plist") {
nsDictionary = NSDictionary(contentsOfFile: path)
}
For Swift 3+
if let path = Bundle.main.path(forResource: "Config", ofType: "plist"),
let myDict = NSDictionary(contentsOfFile: path){
// Use your myDict here
}
And older versions of Swift
var myDict: NSDictionary?
if let path = NSBundle.mainBundle().pathForResource("Config", ofType: "plist") {
myDict = NSDictionary(contentsOfFile: path)
}
if let dict = myDict {
// Use your dict here
}
The NSClasses are still available and perfectly fine to use in Swift. I think they'll probably want to shift focus to swift soon, but currently the swift APIs don't have all the functionality of the core NSClasses.
This is what I do if I want to convert a .plist to a Swift dictionary:
if let path = NSBundle.mainBundle().pathForResource("Config", ofType: "plist") {
if let dict = NSDictionary(contentsOfFile: path) as? Dictionary<String, AnyObject> {
// use swift dictionary as normal
}
}
Edited for Swift 2.0:
if let path = NSBundle.mainBundle().pathForResource("Config", ofType: "plist"), dict = NSDictionary(contentsOfFile: path) as? [String: AnyObject] {
// use swift dictionary as normal
}
Edited for Swift 3.0:
if let path = Bundle.main.path(forResource: "Config", ofType: "plist"), let dict = NSDictionary(contentsOfFile: path) as? [String: AnyObject] {
// use swift dictionary as normal
}
Swift 4.0
You can now use the Decodable protocol to Decode a .plist into a custom struct. I will go over a basic example, for more complicated .plist structures I recommend reading up on Decodable/Encodable (a good resource is here: https://benscheirman.com/2017/06/swift-json/).
First setup your struct into the format of your .plist file. For this example I will consider a .plist with a root level Dictionary and 3 entries: 1 String with key "name", 1 Int with key "age", and 1 Boolean with key "single". Here is the struct:
struct Config: Decodable {
private enum CodingKeys: String, CodingKey {
case name, age, single
}
let name: String
let age: Int
let single: Bool
}
Simple enough. Now the cool part. Using the PropertyListDecoder class we can easily parse the .plist file into an instantiation of this struct:
func parseConfig() -> Config {
let url = Bundle.main.url(forResource: "Config", withExtension: "plist")!
let data = try! Data(contentsOf: url)
let decoder = PropertyListDecoder()
return try! decoder.decode(Config.self, from: data)
}
Not much more code to worry about, and its all in Swift. Better yet we now have an instantiation of the Config struct that we can easily use:
let config = parseConfig()
print(config.name)
print(config.age)
print(config.single)
This Prints the value for the "name", "age", and "single" keys in the .plist.
In swift 3.0 Reading from Plist.
func readPropertyList() {
var propertyListFormat = PropertyListSerialization.PropertyListFormat.xml //Format of the Property List.
var plistData: [String: AnyObject] = [:] //Our data
let plistPath: String? = Bundle.main.path(forResource: "data", ofType: "plist")! //the path of the data
let plistXML = FileManager.default.contents(atPath: plistPath!)!
do {//convert the data to a dictionary and handle errors.
plistData = try PropertyListSerialization.propertyList(from: plistXML, options: .mutableContainersAndLeaves, format: &propertyListFormat) as! [String:AnyObject]
} catch {
print("Error reading plist: \(error), format: \(propertyListFormat)")
}
}
Read More
HOW TO USE PROPERTY LISTS (.PLIST) IN SWIFT.
This answer uses Swift native objects rather than NSDictionary.
Swift 3.0
//get the path of the plist file
guard let plistPath = Bundle.main.path(forResource: "level1", ofType: "plist") else { return }
//load the plist as data in memory
guard let plistData = FileManager.default.contents(atPath: plistPath) else { return }
//use the format of a property list (xml)
var format = PropertyListSerialization.PropertyListFormat.xml
//convert the plist data to a Swift Dictionary
guard let plistDict = try! PropertyListSerialization.propertyList(from: plistData, options: .mutableContainersAndLeaves, format: &format) as? [String : AnyObject] else { return }
//access the values in the dictionary
if let value = plistDict["aKey"] as? String {
//do something with your value
print(value)
}
//you can also use the coalesce operator to handle possible nil values
var myValue = plistDict["aKey"] ?? ""
I have been working with Swift 3.0 and wanted to contribute an answer for the updated syntax. Additionally, and possibly more importantly, I am using the PropertyListSerialization object to do the heavy lifting, which is a lot more flexible than just using the NSDictionary as it allows for an Array as the root type of the plist.
Below is a screenshot of the plist I am using. It is a little complicated, so as to show the power available, but this will work for any allowable combination of plist types.
As you can see I am using an Array of String:String dictionaries to store a list of website names and their corresponding URL.
I am using the PropertyListSerialization object, as mentioned above, to do the heavy lifting for me. Additionally, Swift 3.0 has become more "Swifty" so all of the object names have lost the "NS" prefix.
let path = Bundle.main().pathForResource("DefaultSiteList", ofType: "plist")!
let url = URL(fileURLWithPath: path)
let data = try! Data(contentsOf: url)
let plist = try! PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil)
After the above code runs plist will be of type Array<AnyObject>, but we know what type it really is so we can cast it to the correct type:
let dictArray = plist as! [[String:String]]
// [[String:String]] is equivalent to Array< Dictionary<String, String> >
And now we can access the various properties of our Array of String:String Dictionaries in a natural way. Hopefully to convert them into actual strongly typed structs or classes ;)
print(dictArray[0]["Name"])
Swift 5
If you want to fetch specific value for some key then we can use below extension which uses infoDictionary property on Bundle.
Bundle.main.infoDictionary can be used to get all info.plist values in the form dictionary and so we can directly query using object(forInfoDictionaryKey: key) method on Bundle
extension Bundle {
static func infoPlistValue(forKey key: String) -> Any? {
guard let value = Bundle.main.object(forInfoDictionaryKey: key) else {
return nil
}
return value
}
}
Usage
guard let apiURL = Bundle.infoPlistValue(forKey: "API_URL_KEY") as? String else { return }
It is best to use native dictionaries and arrays because they have been optimized for use with swift. That being said you can use NS... classes in swift and I think this situation warrants that. Here is how you would implement it:
var path = NSBundle.mainBundle().pathForResource("Config", ofType: "plist")
var dict = NSDictionary(contentsOfFile: path)
So far (in my opinion) this is the easiest and most efficient way to access a plist, but in the future I expect that apple will add more functionality (such as using plist) into native dictionaries.
Swift - Read/Write plist and text file....
override func viewDidLoad() {
super.viewDidLoad()
let fileManager = (NSFileManager .defaultManager())
let directorys : [String]? = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory,NSSearchPathDomainMask.AllDomainsMask, true) as? [String]
if (directorys != nil){
let directories:[String] = directorys!;
let dictionary = directories[0]; //documents directory
// Create and insert the data into the Plist file ....
let plistfile = "myPlist.plist"
var myDictionary: NSMutableDictionary = ["Content": "This is a sample Plist file ........."]
let plistpath = dictionary.stringByAppendingPathComponent(plistfile);
if !fileManager .fileExistsAtPath(plistpath){//writing Plist file
myDictionary.writeToFile(plistpath, atomically: false)
}
else{ //Reading Plist file
println("Plist file found")
let resultDictionary = NSMutableDictionary(contentsOfFile: plistpath)
println(resultDictionary?.description)
}
// Create and insert the data into the Text file ....
let textfile = "myText.txt"
let sampleText = "This is a sample text file ......... "
let textpath = dictionary.stringByAppendingPathComponent(textfile);
if !fileManager .fileExistsAtPath(textpath){//writing text file
sampleText.writeToFile(textpath, atomically: false, encoding: NSUTF8StringEncoding, error: nil);
} else{
//Reading text file
let reulttext = String(contentsOfFile: textpath, encoding: NSUTF8StringEncoding, error: nil)
println(reulttext)
}
}
else {
println("directory is empty")
}
}
Swift 2.0 : Accessing Info.Plist
I have a Dictionary named CoachMarksDictionary with a boolean value in Info.Plist . I want to access the bool value and make it true.
let path = NSBundle.mainBundle().pathForResource("Info", ofType: "plist")!
let dict = NSDictionary(contentsOfFile: path) as! [String: AnyObject]
if let CoachMarksDict = dict["CoachMarksDictionary"] {
print("Info.plist : \(CoachMarksDict)")
var dashC = CoachMarksDict["DashBoardCompleted"] as! Bool
print("DashBoardCompleted state :\(dashC) ")
}
Writing To Plist:
From a Custom Plist:-
(Make from File-New-File-Resource-PropertyList. Added three strings named : DashBoard_New, DashBoard_Draft, DashBoard_Completed)
func writeToCoachMarksPlist(status:String?,keyName:String?)
{
let path1 = NSBundle.mainBundle().pathForResource("CoachMarks", ofType: "plist")
let coachMarksDICT = NSMutableDictionary(contentsOfFile: path1!)! as NSMutableDictionary
var coachMarksMine = coachMarksDICT.objectForKey(keyName!)
coachMarksMine = status
coachMarksDICT.setValue(status, forKey: keyName!)
coachMarksDICT.writeToFile(path1!, atomically: true)
}
The method can be called as
self.writeToCoachMarksPlist(" true - means user has checked the marks",keyName: "the key in the CoachMarks dictionary").
Converted into a convenience extension via Nick's answer:
extension Dictionary {
static func contentsOf(path: URL) -> Dictionary<String, AnyObject> {
let data = try! Data(contentsOf: path)
let plist = try! PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil)
return plist as! [String: AnyObject]
}
}
usage:
let path = Bundle.main.path(forResource: "plistName", ofType: "plist")!
let url = URL(fileURLWithPath: path)
let dict = Dictionary<String, AnyObject>.contentsOf(path: url)
I'd be willing to bet that it would also work to create a similar extension for Arrays
Since this answer isn't here yet, just wanted to point out you can also use the infoDictionary property to get the info plist as a dictionary, Bundle.main.infoDictionary.
Although something like Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String) may be faster if you're only interested in a specific item in the info plist.
// Swift 4
// Getting info plist as a dictionary
let dictionary = Bundle.main.infoDictionary
// Getting the app display name from the info plist
Bundle.main.infoDictionary?[kCFBundleNameKey as String]
// Getting the app display name from the info plist (another way)
Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String)
can actually do it in 1 line
var dict = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("Config", ofType: "plist"))
You can read plist in SWIFT Language in this way:
let path = NSBundle.mainBundle().pathForResource("PriceList", ofType: "plist")
let dict = NSDictionary(contentsOfFile: path)
Read Single Dictionary value:
let test: AnyObject = dict.objectForKey("index1")
If you want to get full multi-dimensional dictionary in plist:
let value: AnyObject = dict.objectForKey("index2").objectForKey("date")
Here is the plist:
<plist version="1.0">
<dict>
<key>index2</key>
<dict>
<key>date</key>
<string>20140610</string>
<key>amount</key>
<string>110</string>
</dict>
<key>index1</key>
<dict>
<key>amount</key>
<string>125</string>
<key>date</key>
<string>20140212</string>
</dict>
</dict>
</plist>
in my case I create a NSDictionary called appSettings and add all needed keys. For this case, the solution is:
if let dict = NSBundle.mainBundle().objectForInfoDictionaryKey("appSettings") {
if let configAppToken = dict["myKeyInsideAppSettings"] as? String {
}
}
Step 1 : Simple and fastest way to parse plist in swift 3+
extension Bundle {
func parsePlist(ofName name: String) -> [String: AnyObject]? {
// check if plist data available
guard let plistURL = Bundle.main.url(forResource: name, withExtension: "plist"),
let data = try? Data(contentsOf: plistURL)
else {
return nil
}
// parse plist into [String: Anyobject]
guard let plistDictionary = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: AnyObject] else {
return nil
}
return plistDictionary
}
}
Step 2: How to use:
Bundle().parsePlist(ofName: "Your-Plist-Name")
You can use that, I create a simple extension for Dictionary in github https://github.com/DaRkD0G/LoadExtension
extension Dictionary {
/**
Load a Plist file from the app bundle into a new dictionary
:param: File name
:return: Dictionary<String, AnyObject>?
*/
static func loadPlistFromProject(filename: String) -> Dictionary<String, AnyObject>? {
if let path = NSBundle.mainBundle().pathForResource("GameParam", ofType: "plist") {
return NSDictionary(contentsOfFile: path) as? Dictionary<String, AnyObject>
}
println("Could not find file: \(filename)")
return nil
}
}
And you can use that for load
/**
Example function for load Files Plist
:param: Name File Plist
*/
func loadPlist(filename: String) -> ExampleClass? {
if let dictionary = Dictionary<String, AnyObject>.loadPlistFromProject(filename) {
let stringValue = (dictionary["name"] as NSString)
let intergerValue = (dictionary["score"] as NSString).integerValue
let doubleValue = (dictionary["transition"] as NSString).doubleValue
return ExampleClass(stringValue: stringValue, intergerValue: intergerValue, doubleValue: doubleValue)
}
return nil
}
Here is a bit shorter version, based on #connor 's answer
guard let path = Bundle.main.path(forResource: "GoogleService-Info", ofType: "plist"),
let myDict = NSDictionary(contentsOfFile: path) else {
return nil
}
let value = dict.value(forKey: "CLIENT_ID") as! String?
Swift 3.0
if let path = Bundle.main.path(forResource: "config", ofType: "plist") {
let dict = NSDictionary(contentsOfFile: path)
// use dictionary
}
The easiest way to do this in my opinion.
I've created a simple Dictionary initializer that replaces NSDictionary(contentsOfFile: path). Just remove the NS.
extension Dictionary where Key == String, Value == Any {
public init?(contentsOfFile path: String) {
let url = URL(fileURLWithPath: path)
self.init(contentsOfURL: url)
}
public init?(contentsOfURL url: URL) {
guard let data = try? Data(contentsOf: url),
let dictionary = (try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any]) ?? nil
else { return nil }
self = dictionary
}
}
You can use it like so:
let filePath = Bundle.main.path(forResource: "Preferences", ofType: "plist")!
let preferences = Dictionary(contentsOfFile: filePath)!
UserDefaults.standard.register(defaults: preferences)
Swift 4.0 iOS 11.2.6 list parsed and code to parse it, based on https://stackoverflow.com/users/3647770/ashok-r answer above.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>identity</key>
<string>blah-1</string>
<key>major</key>
<string>1</string>
<key>minor</key>
<string>1</string>
<key>uuid</key>
<string>f45321</string>
<key>web</key>
<string>http://web</string>
</dict>
<dict>
<key>identity</key>
<string></string>
<key>major</key>
<string></string>
<key>minor</key>
<string></string>
<key>uuid</key>
<string></string>
<key>web</key>
<string></string>
</dict>
</array>
</plist>
do {
let plistXML = try Data(contentsOf: url)
var plistData: [[String: AnyObject]] = [[:]]
var propertyListFormat = PropertyListSerialization.PropertyListFormat.xml
do {
plistData = try PropertyListSerialization.propertyList(from: plistXML, options: .mutableContainersAndLeaves, format: &propertyListFormat) as! [[String:AnyObject]]
} catch {
print("Error reading plist: \(error), format: \(propertyListFormat)")
}
} catch {
print("error no upload")
}
Here's the solution I found:
let levelBlocks = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("LevelBlocks", ofType: "plist"))
let test: AnyObject = levelBlocks.objectForKey("Level1")
println(test) // Prints the value of test
I set the type of test to AnyObject to silence a warning about an unexpected inference that could occur.
Also, it has to be done in a class method.
To access and save a specific value of a known type:
let value = levelBlocks.objectForKey("Level1").objectForKey("amount") as Int
println(toString(value)) // Converts value to String and prints it
I use swift dictionaries but convert them to and from NSDictionaries in my file manager class like so:
func writePlist(fileName:String, myDict:Dictionary<String, AnyObject>){
let docsDir:String = dirPaths[0] as String
let docPath = docsDir + "/" + fileName
let thisDict = myDict as NSDictionary
if(thisDict.writeToFile(docPath, atomically: true)){
NSLog("success")
} else {
NSLog("failure")
}
}
func getPlist(fileName:String)->Dictionary<String, AnyObject>{
let docsDir:String = dirPaths[0] as String
let docPath = docsDir + "/" + fileName
let thisDict = NSDictionary(contentsOfFile: docPath)
return thisDict! as! Dictionary<String, AnyObject>
}
This seems the least troubling way to read and write but let's the rest of my code stay as swift as possible.
Plist is a simple Swift enum I made for working with property lists.
// load an applications info.plist data
let info = Plist(NSBundle.mainBundle().infoDictionary)
let identifier = info["CFBundleIndentifier"].string!
More examples:
import Plist
// initialize using an NSDictionary
// and retrieve keyed values
let info = Plist(dict)
let name = info["name"].string ?? ""
let age = info["age"].int ?? 0
// initialize using an NSArray
// and retrieve indexed values
let info = Plist(array)
let itemAtIndex0 = info[0].value
// utility initiaizer to load a plist file at specified path
let info = Plist(path: "path_to_plist_file")
// we support index chaining - you can get to a dictionary from an array via
// a dictionary and so on
// don't worry, the following will not fail with errors in case
// the index path is invalid
if let complicatedAccessOfSomeStringValueOfInterest = info["dictKey"][10]["anotherKey"].string {
// do something
}
else {
// data cannot be indexed
}
// you can also re-use parts of a plist data structure
let info = Plist(...)
let firstSection = info["Sections"][0]["SectionData"]
let sectionKey = firstSection["key"].string!
let sectionSecret = firstSection["secret"].int!
Plist.swift
Plist itself is quite simple, here's its listing in case you to refer directly.
//
// Plist.swift
//
import Foundation
public enum Plist {
case dictionary(NSDictionary)
case Array(NSArray)
case Value(Any)
case none
public init(_ dict: NSDictionary) {
self = .dictionary(dict)
}
public init(_ array: NSArray) {
self = .Array(array)
}
public init(_ value: Any?) {
self = Plist.wrap(value)
}
}
// MARK:- initialize from a path
extension Plist {
public init(path: String) {
if let dict = NSDictionary(contentsOfFile: path) {
self = .dictionary(dict)
}
else if let array = NSArray(contentsOfFile: path) {
self = .Array(array)
}
else {
self = .none
}
}
}
// MARK:- private helpers
extension Plist {
/// wraps a given object to a Plist
fileprivate static func wrap(_ object: Any?) -> Plist {
if let dict = object as? NSDictionary {
return .dictionary(dict)
}
if let array = object as? NSArray {
return .Array(array)
}
if let value = object {
return .Value(value)
}
return .none
}
/// tries to cast to an optional T
fileprivate func cast<T>() -> T? {
switch self {
case let .Value(value):
return value as? T
default:
return nil
}
}
}
// MARK:- subscripting
extension Plist {
/// index a dictionary
public subscript(key: String) -> Plist {
switch self {
case let .dictionary(dict):
let v = dict.object(forKey: key)
return Plist.wrap(v)
default:
return .none
}
}
/// index an array
public subscript(index: Int) -> Plist {
switch self {
case let .Array(array):
if index >= 0 && index < array.count {
return Plist.wrap(array[index])
}
return .none
default:
return .none
}
}
}
// MARK:- Value extraction
extension Plist {
public var string: String? { return cast() }
public var int: Int? { return cast() }
public var double: Double? { return cast() }
public var float: Float? { return cast() }
public var date: Date? { return cast() }
public var data: Data? { return cast() }
public var number: NSNumber? { return cast() }
public var bool: Bool? { return cast() }
// unwraps and returns the underlying value
public var value: Any? {
switch self {
case let .Value(value):
return value
case let .dictionary(dict):
return dict
case let .Array(array):
return array
case .none:
return nil
}
}
// returns the underlying array
public var array: NSArray? {
switch self {
case let .Array(array):
return array
default:
return nil
}
}
// returns the underlying dictionary
public var dict: NSDictionary? {
switch self {
case let .dictionary(dict):
return dict
default:
return nil
}
}
}
// MARK:- CustomStringConvertible
extension Plist : CustomStringConvertible {
public var description:String {
switch self {
case let .Array(array): return "(array \(array))"
case let .dictionary(dict): return "(dict \(dict))"
case let .Value(value): return "(value \(value))"
case .none: return "(none)"
}
}
}
Swift 3.0
if you want to read a "2-dimensional Array" from .plist, you can try it like this:
if let path = Bundle.main.path(forResource: "Info", ofType: "plist") {
if let dimension1 = NSDictionary(contentsOfFile: path) {
if let dimension2 = dimension1["key"] as? [String] {
destination_array = dimension2
}
}
}
If you have Info.plist, then use
Bundle.main.infoDictionary
Simple struct to access plist file (Swift 2.0)
struct Configuration {
static let path = NSBundle.mainBundle().pathForResource("Info", ofType: "plist")!
static let dict = NSDictionary(contentsOfFile: path) as! [String: AnyObject]
static let someValue = dict["someKey"] as! String
}
Usage:
print("someValue = \(Configuration.someValue)")

Resources