Encrypted backup

Hi, I'd like to offer my users the option to save an encrypted backup of their data, from CoreData, to a place of their choosing. Just a textfile with the data in json format. How I envisage it is that they will click the backup button, a popup will ask them to input a password, then they'll save the outputted file somewhere. I don't know how to implement this. I've been playing around with CryptoKit in a playground, but tutorials seem few and far between. What is available isn't geared towards explaining how to do what I want to do. Does anyone know of a resource or tutorial that could help me learn to implement this feature? Any help much appreciated.

Replies

The droid you’re looking for here is

AES.GCM.SealedBox
. This will take data and encrypt it using AES-GCM, our recommended form of symmetric encryption (critically, it’s authenticated encryption).

The encryption key is represented by a

SymmetricKey
value. Unfortunately there is no easy way to derive this directly from a password. The standard way to do this, PBKDF2, isn’t supported in Apple CryptoKit (r. 51744434).

This means you have to use CommonCrypto to do the key derivation. This is a lot less pleasant than CryptoKit. To get you started I’ve pasted in a wrapper below.

IMPORTANT Make sure you supply salt and choose the rounds wisely. See the Wikipedia article for some advice on that front.

Also, there are better key derivation algorithms you might use for this problem — things like scrypt — but none of those have APIs in the Apple SDKs. You might want to look into third-party implementations.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
func pbkdf2SHA256Key(size: Int, from password: String, salt: Data, rounds: Int) throws -> Data {
    precondition(size != 0)         // Otherwise `baseAddress` could be `nil`.
    let rounds32 = UInt32(rounds)   // We trap if you ask for way too many.
    return try password.withCString { passwordPtr in
        return try salt.withUnsafeBytes { saltBuf in
            var output = [UInt8](repeating: 0, count: size)
            let err = CCKeyDerivationPBKDF(
                CCPBKDFAlgorithm(kCCPBKDF2),
                passwordPtr, strlen(passwordPtr),
                saltBuf.baseAddress!.assumingMemoryBound(to: UInt8.self),
                saltBuf.count,
                CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256),
                rounds32,
                &output, output.count
            )
            guard err == 0 else {
                throw NSError(domain: NSOSStatusErrorDomain, code: Int(err), userInfo: nil)
            }
            return Data(output)
        }
    }
}

Thanks heaps, Eskimo.
I'll take a look at it all and see if I can nut it out. I'm very much a novice, so I may have some more questions after I've tackled what you've given me.
Cheers

I think I've got the logic of it now. Because I'm a bit slow, could you please correct me if I have this wrong.
1. A key is derived from a password.
2. It's used to encrypt the textfile.

3. It can then be gotten rid of, because when it comes to decryption the same key will be derived from password input again and used to decrypt the file?

It can then be gotten rid of, because when it comes to decryption the same key will be derived from password input again and used to decrypt the file?

Correct. However, you do need to keep track of the salt and rounds value that you used to derive the key from the password. These don’t need to be protected though; you can just store them in the clear.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks heaps. I have it running nicely in the playground. I spent quite a bit of time going down a few deadends trying to work out how to save it, until I cottoned on to saving it as Data. I hope you don't mind if I ask a few more questions.
I've saved it this way:

let filename = getDocumentsDirectory().appendingPathComponent("backup.txt")

//Write to file

do {

let e = Data(encryptedData.combined!)

try e.write(to: filename)
Is saving it to a standard .txt file okay?
Also, for arguments sake, if the salt and rounds needed to be hidden, how would that be handled? I don't know anything about what happens when an app is installed, but I imagine the salt and rounds could be stored in a file and then, during installation, written to the keychain while the file is deleted, or would that be insecure? I guess that any values that required high security could be encrypted and then unencrypted during the installation process for passing into the keychain.
Sorry for so many questions, but I have one more. I know that the iPhone is encrypted as a whole, but if I wanted to have encryption on top of that, within my app, is cryptokit suitable for implementing that, or would common crypto be the better option?

Is saving it to a standard

.txt
file okay?

It seems like a poor choice given that your encrypted data isn’t actually text.

Also, for arguments sake, if the salt and rounds needed to be hidden, how would that be handled?

I would embed the salt and rounds in the same file as the data itself. If your data is small, you could use a JSON file for all of this. If it’s large, you’ll probably want something more efficient.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Cheers. It's a poor choice, but I have no idea what would be the right choice. If it was an image then it's jpeg, or png, or something obvious, but with lots of googling and time I still haven't tracked down what an encrypted data file ought to be.

There is no widespread standard for encrypted files. Thus, you need to make up your own file format, and thus choose your own extension.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Now to hunt down some info on how to do it.
Thanks for all your help.