DEV Community

Felipe Menezes
Felipe Menezes

Posted on

Security iOS Apps with OWASP Best Practices for Data Protection

After several years as iOS developer, I decided revisit some concepts of secure development, and explore some advanced topics, using OWASP as a reference for best practices. In this article, we'll start by securely saving data on device.

Secure Data Storage

At this point, you already know that the UserDefaults API is not the right option to save sensitive data. What data are those? All related to personal user information or any sensitive data that could be traced back to it (name, credentials, health, address, banking, financial information), passwords and so on. This also applies to data in any documents too.

To handle this, we have some options, from the basic to more advanced. Here we go:

Securing using Keychain

The most secure way to store simple data is to use the Keychain, which is a data storage system in iOS that uses an encrypted database protected by hardware encryption. It's the most common and well-known way to store key-value pairs safety, and developers usually takes advantage of popular libraries like KeychainSwift.

But, when exploring protection parameters, consider these options to ensure your data is truly secure using the default api. Take a look at this:

func saveToKeychain(userName: String) {
        guard let data = userName.data(using: .utf8) else {
                return
            }
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: account,
            kSecValueData as String: data,
            kSecAttrAccessible as String: kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
        ]
        SecItemAdd(query as CFDictionary, nil)
    }
Enter fullscreen mode Exit fullscreen mode

The use of kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly is the best way to secure sensitive data because it forces the device to guarantee that the user has set a passcode on the device. However, if the user disables it, your data is erased. Alternatively, use kSecAttrAccessibleWhenUnlockedThisDeviceOnly for other cases while keeping your data saved only on the device, not shared with iCloud.

  • Because it is not shared with iCloud, keep in mind that if you want this data to be recovered during a device backup, you need to choose other options.

And if you want to use it in another application (that you own) or widget, you can include kSecAttrAccessGroup as String: accessGroup to give access to it (other project settings are required).

We can requires the user’s presence to be saved or retrieved, you can also define the .userPresence flag, which certifies that the user is present by prompting for authentication methods like Face ID, passcode, or Touch ID.

Also, you can include an application-specific password to retrieve this data, different from the user’s passcode.

guard let access = SecAccessControlCreateWithFlags(
  nil,  // Use the default allocator.
  kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
  .userPresence, &error) else {
   return
}
Enter fullscreen mode Exit fullscreen mode

Don’t forget to wipe out your data if the user log out or reinstalls your application.

The Keychain is a global storage at the iOS level (though not shared between apps by default). Some developers overlook this aspect of the lifecycle, allowing data to be accessed on the same device by different users. Here’s a simple way to handle this here

FileStorage

For store a document file, like a PDF or other documents, you can use this code to securely save it in file storage. In some cases, you collect those files from the user to send later (or sync them). Below is a sample to securely save the file:

func saveFileDataStorage(fromUrl: URL) {
    let fileManager = FileManager.default
    do {
        let documentsUrl = try fileManager.url(for: .documentDirectory,
                                               in: .userDomainMask,
                                               appropriateFor: nil, create: false)
        let destinationFileUrl = documentsUrl.appendingPathComponent("myFile.pdf")
        if fileManager.fileExists(atPath: destinationFileUrl.path) {
            try fileManager.removeItem(at: destinationFileUrl)
        }
        try fileManager.moveItem(at: fromUrl, to: destinationFileUrl)
        let attributes = [FileAttributeKey.protectionKey: FileProtectionType.complete]
        try fileManager.setAttributes(attributes, ofItemAtPath: destinationFileUrl.path)

        print("File saved successfully at \(destinationFileUrl.path)")
    } catch {
        print("File Error: \(error.localizedDescription)")
    }
}
Enter fullscreen mode Exit fullscreen mode

The most important parameter is FileProtectionType.complete, which marks your file to be accessible only when user has unlocked the device. If the device is locked, the data becomes inaccessible until the next unlock.

A good explanation can be found here and in this Apple article.

Note that the default protection since iOS 7 is Protected Until First User Authentication, which is not the best choice to fully protect your document.

Creating and Secure Private Keys (SecureEnclave and Keychain)

Is possible to use the Keychain to generate a private key (or other types of encryption keys) to secure data in iOS and save it into Secure Enclave, the same chip that stores iOS biometric data (passcode, Touch ID, or Face ID).

Private keys stored in the Secure Enclave are non-extractable, and cryptographic operations (e.g., signing, decryption) are performed inside it.

Additionally, the private key material never leaves the Secure Enclave, reducing the risk of key compromise.

The code below shows the piece of the attribute dictionary that generates the key: more details are available on the github project.

let attributes: [String: Any] = [
    kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
    kSecAttrKeySizeInBits as String: 256,
    kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
    kSecPrivateKeyAttrs as String: [
        kSecAttrIsPermanent as String: true,
        kSecAttrApplicationTag as String: account.data(using: .utf8)!,
        kSecAttrAccessControl as String: accessControl
    ]
]
Enter fullscreen mode Exit fullscreen mode

Decrypt with private key

Keep in mind, that this kind of storage is designed only for private keys, and use to be rare necessarily for common develop requirements.

Secure your CoreData database

It's not very common, but perhaps you use CoreData to save more structured data and its relations, and need to secure it. CoreData does not encrypt its data by default.

Start securing the file of your database in persistent container, defining a protection level for it, like below, using FileProtectionType.complete key:

let storeURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!.appendingPathComponent("iOSOwaspSec.sqlite")
    let description = NSPersistentStoreDescription(url: storeURL)
        description.setOption(FileProtectionType.complete as NSObject, 
        forKey: NSPersistentStoreFileProtectionKey)
    container.persistentStoreDescriptions = [description]
Enter fullscreen mode Exit fullscreen mode

If your app needs to access data while in the background or when the device is locked, choose an appropriate protection level. However, you can encrypt the data even when the device is unlocked.

For higher security, consider encrypting the persistent store. This technique uses the SQLCipher library to encrypt the entire database, but with a performance cost. In OWASP we have a link to github project that do this.

But, if you want just to secure the information in field level of your table, without lock all database with performance cost, you can create a ValueTransformer:

@objc(SecureTransformer)
class SecureTransformer: ValueTransformer {
    static let key = static var key: SymmetricKey = {
        // Retrieve the key from the Keychain or generate a new one
        if let storedKeyData = KeychainHelper
            .retrieveKey(alias: KeychainHelper.keyAlias) {
               return SymmetricKey(data: storedKeyData)
           } else {
               let newKey = SymmetricKey(size: .bits256)
               let keyData = newKey.withUnsafeBytes { Data($0) }
               KeychainHelper.storeKey(keyData,
                                       alias: KeychainHelper.keyAlias)
               return newKey
           }
       }()

    override class func transformedValueClass() -> AnyClass {
        return NSData.self
    }
    override func transformedValue(_ value: Any?) -> Any? {
        guard let stringValue = value as? String else { return nil }
        let data = Data(stringValue.utf8)
        guard let sealedBox = try? ChaChaPoly.seal(data, using: SecureTransformer.key) else { return nil }
        return sealedBox.combined as NSData
    }
    override func reverseTransformedValue(_ value: Any?) -> Any? {
        guard let data = value as? Data else { return nil }
        guard let sealedBox = try? ChaChaPoly.SealedBox(combined: data),
              let decryptedData = try? ChaChaPoly.open(sealedBox, using: SecureTransformer.key) else { return nil }
        return String(data: decryptedData, encoding: .utf8)
    }
}
Enter fullscreen mode Exit fullscreen mode

And as show in the image, set the attribute’s type to Transformable, and Set the Value Transformer to SecureTransformer.

Transformable in CoreData Settings

And include the register of your transformer at some point on initialization:

ValueTransformer.setValueTransformer(SecureTransformer(), 
forName: NSValueTransformerName("SecureTransformer"))
Enter fullscreen mode Exit fullscreen mode

Now you can secure save a specific item in your CoreDate table, this comes handy and avoid encrypt the entire database.

Conclusion

This article cover some best praticies of OWASP for secure your data in iOS application's.

Check the github project here.

And here’s a challenge for you: Can you change the project to use the private key to securely save an item in Core Data, or maybe perform another cryptographic operation?

Top comments (0)