Encrypt data

Because it gives zero value and is just error prone?

Why do you think this is better?

let key = try encryptionKeyClient.get(identifier: "if you make a typo here no key is found")

vs

let key = try encryptionKeyClient.get()

?

The first alternative has no advantage at all over the second. And is just bad code.

At LEAST what you wanna do is:

extension String {
    static let encryptionKeyIdentifier = "my.key"
}

and then:

let key = try encryptionKeyClient.get(identifier: .encryptionKeyIdentifier)

But it still gives you no advantage what so ever over

let key = try encryptionKeyClient.get()

The notion of an identifier - or rather "persistence key" is something a KeychainClient should concern itself with. Not the EncryptionKeyClient. So you are conflating things a bit.

No, not at all. My best beginners advice for you is: Never use inheritance, ever. In fact, never use class ever. You get side effects very easily which makes code hard to follow, understand and reason with.And do use swift dependencies for "managers", it makes it easy to mock and test.

So instead of inheritance - is a - use composition - has a.

The KeychainClient is a testable wrapper around keychain, essentially CRUD (Create, Read, Update, Delete) operations on Data for some persistence key.

The EncryptionKeyClient has a KeychainClient (depends on), which you use like this with swift dependencies:

import DependenciesMacros

@DependencyClient
public struct KeychainClient {
  var save: (String, Data) throws
  var load: (String) throws -> Data?
  // delete / update
}

@DependencyClient
public struct EncryptionKeyClient {
  var get: () throws -> SymmetricKey
}

extension EncryptionKeyClient {
    public let liveValue = Self(
        get: {
            @Dependency(KeychainClient.self) var keychainClient
            let persistenceKey = "my.encryption.key"
            if let data = try keychainClient.load(persistenceKey) {
                   guard data.count == 32 else { throw Error.invalidLength }
                   return SymmetricKey(data: data)
            } else {
                    let newKey = SymmetricKey(size: .bits256)
                    let keyData = newKey.withUnsafeBytes { Data($0 }
                    try keychainClient.save(persistenceKey, keyData)
                    return newKey
            }
        }
    )
}

@DependencyClient
public struct EncryptionClient {
  var encryptWithDetails: (_ plaintext: Data, _ nonce: Data?, _ tag: Data?) throws -> Data
  var decrypt: (Data) throws -> Data
} 

extension EncryptionClient {
    public func encrypt(data: Data) throws -> Data {
         try self.encryptWithDetails(data, nil, nil)
    }
    public let liveValue: Self = {
             @Dependency(EncryptionKeyClient) var encryptionKeyClient
             return Self(
                    encryptWithDetails: { (plaintext, nonce, tag) in
                           if let nonce {
                                  // https://github.com/apple/swift-crypto/blob/a84771015fc5e2823946e83fb3db80c597c432ec/Sources/Crypto/AEADs/AES/GCM/AES-GCM.swift#L30-L31
                                  precondition(nonce.count, 12)
                           } 
                    if let tag {
                                  // https://github.com/apple/swift-crypto/blob/a84771015fc5e2823946e83fb3db80c597c432ec/Sources/Crypto/AEADs/AES/GCM/AES-GCM.swift#L30-L31
                                  precondition(tag, 16)
                           } 
                           let key = try encryptionKeyClient.get()
                           let sealedBox = try AES.GCM.seal(plaintext, using: key, nonce : nonce, authenticating: tag)
                           sealedBox.combined! // safe to force unwrap since we validated length of nonce and tag
                    },
                    decrypt: {
                            let sealedBox = try AES.GCM.SealedBox(combined: $0)
                            let key = try encryptionKeyClient.get()
                            try AES.GCM.open(sealedBox, using: key)
                    },
             )
    }()
}           

This is fully testable! look into documentation of swift dependencies

And in your ViewModel/View(or Reducer if you are using TCA you will simply put

@Dependency(EncryptionClient.self) var encryptionClient

try encryptionClient.decrypt(myEncryptedData)

Put probably you wanna write some HealthDataClient dependency which has a dependency on EncryptionClient and a Supabase client

1 Like