End-to-End Encrypted iOS Chat with Apple’s CryptoKit
In most cases, when building a chat app, it's essential to provide adequate privacy and security to your users. This can be done using cryptographic methods such as end-to-end encryption. End-to-end encryption is becoming a mainstream expectation, as it's featured in the biggest chat apps, such as WhatsApp and Telegram.
In this article, you'll learn how basic end-to-end encryption works in an iOS chat app using Apple's own CryptoKit framework for its secure and high-level encryption methods and Stream's flexible iOS chat SDK for its ready-made chat networking and UI components.
Please note that this tutorial is very basic and strictly educational, may contain simplifications, and rolling your own encryption protocol is not advisable. The algorithms used can contain certain 'gotchas' if not employed properly with the help of security professionals. Still, reading this article is a great start to get practical knowledge.
What Is End-to-End Encryption?
End-to-end encryption is a communication system where the only people who can read the messages are the people communicating. No eavesdropper can access the cryptographic keys needed to decrypt the conversation—not even a company that runs the messaging service.
What Is Apple's CryptoKit?
CryptoKit is a new Swift framework that makes it easier and safer than ever to perform cryptographic operations, whether you simply need to compute a hash or are implementing a more advanced authentication protocol.
What You Need
- Xcode 11 or later
- iOS 13 or later
- A Stream account
Basic Encryption Methods
Before we get to the chat part, let's start by defining the basic encryption methods we need. We'll integrate these methods in the next section.
1. Generating a Private Key
A private key is essential to end-to-end encryption. It is used alongside a public key to generate a symmetric key which is used for encryption and decryption of data. Each user in your application has a private and public key. They share their public keys with each other so that they can generate the same symmetric key. Alice shares her public key with Bob, and Bob uses his private key and Alice's public key to generate a symmetric key. Conversely, Bob shares his public key with Alice, and Alice uses her private key with Bob's public key to generate the same symmetric key. As long as Alice's and Bob's private keys are a secret to each of them, no one can generate the same symmetric key to decrypt their conversations.
To generate a private key, we'll use the
Additionally, I chose the P256 algorithm for its cross-platform availability, as it is supported by the Web Crypto API in case you need a web version. Also, it's a good balance between performance and security. This preference can change with time as new algorithms become available.
PS: The public key can be extracted from the private key using
privateKey.publicKey. We'll touch on this in the integration part of this article.
The private key must be saved somewhere safe. In order to do this, you may need to turn it into a string format to save it and back to perform the cryptographic operations. You can do this with the following
2. Deriving Symmetric Key
Once a user (message sender) has the public key of the other user it wants to communicate with (message recipient), that user can generate the symmetric key using the methods:
privateKey.sharedSecretFromKeyAgreement(with: publicKey) and
This process is called a Diffie-Hellman Key Exchange and ensures the symmetric key can be shared between two users without ever leaving their devices.
3. Encrypt Text
Now, we use that symmetric key for encrypting texts using the
AES.GCM.seal method. We also encode it in a Base64 string, so it can be sent as normal text using the Stream Chat SDK in the integration step.
After the text is encrypted and encoded, it can be safely sent through the network.
4. Decrypt Text
After the recipient receives the encrypted text, they will decode it from Base64 and decrypt it using the
After the text is decrypted, it should be readable and can be displayed in the UI.
Stream Chat Integration
In this section, you'll learn how to use the methods implemented above to achieve end-to-end encryption with Stream Chat's Swift SDK.
You can follow the "iOS Chat SDK Setup" step in the official tutorial to get a basic chat app working in minutes.
1. Public Key Extra Data
We need to define a custom
publicKey field for the User object so that users can share their public key with others. To do this, create the
After that, you should set the
PublicKeyExtraData.self before you initialize the Stream Chat Client in
2. Custom Set User Method
Now, let's define our custom
setUser method so that the user can be registered in Stream Chat alongside its public key.
Additionally, the public key must be exported in a string format. You should define an
exportPublicKey method to do this.
We're percent-encoding the public key after encoding to base64 to ensure the + sign is not ignored in case the public key is used in the URLs of specific requests.
In a future step, after we fetch another user's public key, we'll need to use the
importPublicKey method, which does the reverse of the
3. Encrypted Chat View Controller
Now that we generated the private key and registered our user with a public key, we need a custom
ChatViewController that encrypts messages before sending and decrypts messages before displaying.
To do this, let's create the
EncryptedChatViewController, which inherits from
As you can see, it contains an additional
symmetricKey property, which will be used for the encryption and decryption. In the
viewDidLoad we set the
messagePreparationCallback, which encrypts the messages before they're sent. We also override the
messageCell method to decrypt the message before displaying it.
3. Present Encrypted Chat
After implementing our custom
ChatViewController, now we can present it.
To present the
EncryptedChatViewController, we query the user we want to communicate with to get its public key and generate a symmetric key. After that, we set the
presenter properties and push the view controller into the navigation stack.
Next Steps With CryptoKit
Congratulations! You just learned how to implement basic end-to-end encryption in your iOS chat apps. It's important to know this is the most basic form of end-to-end encryption. It lacks some additional tweaks that can make it more bullet-proof for the real world, such as randomized padding, digital signature, and forward secrecy, among others. Also, for real-world usage, it's vital to get the help of application security professionals.