0

I'm building a simple app, where users can add personal diary entries in the form of text only, and it gets saved to my cloud firestore database. Now, I want to build end-to-end encryption so the user's personal content is never readable by anyone but them.

To achieve this, I'm trying to use the CryptoSwift library to encrypt and decrypt content. For testing purposes, I'm starting with just a simple "Hello World" in my ViewModel. However, I'm unable to get it to work.

Couple of questions

  • I'm unable to get "Hello world" to show up from clear (hello world) > encrypted > cipher > decrypted (hello world). How can I get the same "hello world" result from my decrypt function?

  • In what format should I save my key to the keychain?

  • In what format should I save my encrypted text, and iv on firebase? Should I continue to use .toHexString() or should I be using .toBase64()?

I'm successfully able to generate a key, and save it as a "hexString". And I'm able to "run" through both the encrypt and the decrypt functions without errors, but they don't generate a "hello world" from cleartext(hello world) > encrypt > ciphertext > decrypt > helloworld.

class EncryptionService: EncryptionServicing {
    
    private var keychainService = KeychainService()
    
    func generateKey() throws -> String  {
        print("[EncryptionService]  Generate key called")
        do {
            if let userId = UserService.shared.userInfo?.userId {
                
                let userId: [UInt8] = Array(userId.utf8)
                
                // Salt added for randomness
                let salt: [UInt8] = (1...5).map( {_ in UInt8(Int.random(in: 1...10))} )

                let key = try PKCS5.PBKDF2(
                    password: userId,
                    salt: salt,
                    iterations: 4096,
                    keyLength: 32, /* AES-256 */
                    variant: .sha2(.sha256)
                ).calculate()

                print(" UserId: \(userId)")
                print(" Salt: \(salt)")
                print(" KEY: \(key.toHexString())")

                return key.toHexString()
            }
        }
        catch {
            print(error)
        }
        return ""
    }
    
    func encrypt(clearText: String, aesKey: String) throws -> (cipherText: String, iv: String) {
        print("[EncryptionService] ✅ Encrypt called")
        do {
            let iv = AES.randomIV(AES.blockSize)
            let keyArray = [UInt8](hex: aesKey)
            
            let aes = try AES(key: keyArray, blockMode: CBC(iv: iv), padding: .pkcs7)
            let cipherText = try aes.encrypt(Array(clearText.utf8))
            
            return (cipherText.toHexString(), iv.toHexString())
        }
        catch {
            print(error)
            return ("", "")
        }
    }

    func decrypt(cipherText: String, iv: String, aesKey: String) throws -> String {
        print("[EncryptionService] ✅ Decryption called")
        do {
            print("Ciphertext: \(cipherText)")
            print("Iv: \(iv)")
            print("aesKey: \(aesKey)")
            
            let keyArray = [UInt8](hex: aesKey)
            
            let aes = try AES(key: keyArray, blockMode: CBC(iv: [UInt8](hex: iv)), padding: .pkcs7)
            
            print("AES Key size: \(aes.keySize)")
            
            let decryptedText =  try aes.decrypt(cipherText.bytes)
            
            return decryptedText.toHexString() // doesn't respond with "Hello world!"
        }
    }
}

This is what my ViewModel looks like.

    @Published var dummyClearText: String = "Hello World!"
    @Published var dummyCipherText: String = ""
    @Published var dummyIv: String = ""
    @Published var dummyDecryptedText: String = ""
    
    // Have a "encrypt" button that calls this.
    func generateCipherText() {
        do {
            let result = try EncryptionService().encrypt(clearText: self.dummyClearText, aesKey: self.aesKey)
            self.dummyCipherText = result.cipherText
            self.dummyIv = result.iv
        }
        catch {
            print(error)
        }
    }
    
    // Have a "decrypt" button that calls this, and takes the generated cipher text as input.
    func generateClearText() {
        do {
            let result = try EncryptionService().decrypt(cipherText: self.dummyCipherText, iv: self.dummyIv, aesKey: self.aesKey)
            self.dummyDecryptedText = result // doesn't respond with "Hello world!"
        }
        catch {
            print(error)
        }
    }```

Thank you so much, still very much learning how to Swift. 

Happy to provide more context. 
  • `try aes.encrypt(Array(clearText.utf8))` also looks wrong. It's probably better to properly convert it to data, like `let data = clearText.data(using: .utf8)` (which can fail so handle that), then `try aes.encrypt(data)`. – DarkDust Feb 13 '23 at 08:41

1 Answers1

0

After decryption, you don't return the decrypted text, you return its hex representation:

return decryptedText.toHexString()

You probably want something like this instead:

// Interpreting the data as UTF-8 can fail. You should handle this.
return String(data: decryptedText, encoding: .utf8) ?? ""
DarkDust
  • 90,870
  • 19
  • 190
  • 224