---
id: end_to_end_chat_encryption
sidebar_position: 12
title: End To End Chat Encryption
---

## Introduction

When you communicate over a chat application with another person or group,
you may exchange sensitive information, like personally identifiable information, financial details, or passwords.
A chat application should use end-to-end encryption to ensure that users' data stays secure.

:::note
Before you start, keep in mind that this guide is a basic example intended for educational purposes only.
If you want to implement end-to-end encryption in your production app, please consult a security professional first.
There’s a lot more to consider from a security perspective that isn’t covered here.
:::

## What is End-to-End Encryption?

End-to-end encryption (E2EE) is the process of securing a message from third parties so that only the sender and receiver can access the message.
E2EE provides security by storing the message in an encrypted form on the application's server or database.

You can only access the message by decrypting and signing it using a known public key (distributed freely)
and a corresponding private key (only known by the owner).

Each user in the application has their own public-private key pair.
Public keys are distributed publicly and encrypt the sender’s messages.
The receiver can only decrypt the sender’s message with the matching private key.

Check out the diagram below for an example:

![](../assets/end_to_end_encryption.png)

## Setup

### Dependencies

Add the [web cryptographic](https://pub.dev/packages/webcrypto) package in your `pubspec.yaml` file.

```yaml
dependencies:
  webcrypto: ^0.5.2 # latest version
```

### Generate Key Pair

Write a function that generates a key pair using the **ECDH** algorithm and the **P-256** elliptic curve (**P-256** is well-supported and
offers the right balance of security and performance).

The pair will consist of two keys:
- **PublicKey**: The key that is linked to a user to encrypt messages.
- **PrivateKey**: The key that is stored locally to decrypt messages.

```dart
Future<JsonWebKeyPair> generateKeys() async {
  final keyPair = await EcdhPrivateKey.generateKey(EllipticCurve.p256);
  final publicKeyJwk = await keyPair.publicKey.exportJsonWebKey();
  final privateKeyJwk = await keyPair.privateKey.exportJsonWebKey();

  return JsonWebKeyPair(
    privateKey: json.encode(privateKeyJwk),
    publicKey: json.encode(publicKeyJwk),
  );
}

// Model class for storing keys
class JsonWebKeyPair {
  const JsonWebKeyPair({
    required this.privateKey,
    required this.publicKey,
  });

  final String privateKey;
  final String publicKey;
}
```

### Generate a Cryptographic Key

Next, create a symmetric **Cryptographic Key** using the keys generated in the previous step.
You will use those keys to encrypt and decrypt messages.

```dart
// SendersJwk -> sender.privateKey
// ReceiverJwk -> receiver.publicKey
Future<List<int>> deriveKey(String senderJwk, String receiverJwk) async {
  // Sender's key
  final senderPrivateKey = json.decode(senderJwk);
  final senderEcdhKey = await EcdhPrivateKey.importJsonWebKey(
    senderPrivateKey,
    EllipticCurve.p256,
  );

  // Receiver's key
  final receiverPublicKey = json.decode(receiverJwk);
  final receiverEcdhKey = await EcdhPublicKey.importJsonWebKey(
    receiverPublicKey,
    EllipticCurve.p256,
  );

  // Generating CryptoKey
  final derivedBits = await senderEcdhKey.deriveBits(256, receiverEcdhKey);
  return derivedBits;
}
```

### Encrypting Messages

Once you have generated the **Cryptographic Key**, you're ready to encrypt the message.
You can use the **AES-GCM** algorithm for its known security and performance balance and good browser availability.

```dart
// The "iv" stands for initialization vector (IV). To ensure the encryption’s strength,
// each encryption process must use a random and distinct IV.
// It’s included in the message so that the decryption procedure can use it.
final Uint8List iv = Uint8List.fromList('Initialization Vector'.codeUnits);
```

```dart
Future<String> encryptMessage(String message, List<int> deriveKey) async {
  // Importing cryptoKey
  final aesGcmSecretKey = await AesGcmSecretKey.importRawKey(deriveKey);

  // Converting message into bytes
  final messageBytes = Uint8List.fromList(message.codeUnits);

  // Encrypting the message
  final encryptedMessageBytes =
      await aesGcmSecretKey.encryptBytes(messageBytes, iv);

  // Converting encrypted message into String
  final encryptedMessage = String.fromCharCodes(encryptedMessageBytes);
  return encryptedMessage;
}
```

### Decrypting Messages

Decrypting a message is the opposite of encrypting one.
To decrypt a message to a human-readable format, use the code snippet below:

```dart
Future<String> decryptMessage(String encryptedMessage, List<int> deriveKey) async {
  // Importing cryptoKey
  final aesGcmSecretKey = await AesGcmSecretKey.importRawKey(deriveKey);

  // Converting message into bytes
  final messageBytes = Uint8List.fromList(encryptedMessage.codeUnits);

  // Decrypting the message
  final decryptedMessageBytes =
      await aesGcmSecretKey.decryptBytes(messageBytes, iv);

  // Converting decrypted message into String
  final decryptedMessage = String.fromCharCodes(decryptedMessageBytes);
  return decryptedMessage;
}
```

## Implement as a Stream Chat Feature

Now that your setup is complete you can use it to implement end-to-end encryption in your app.

### Store User's Public Key

The first thing you need to do is store the generated `publicKey` as an `extraData` property, in order
for other users to encrypt messages.

```dart
// Generating keyPair using the function defined in above steps
final keyPair = generateKeys();
```

```dart
await client.connectUser(
  User(
    id: 'cool-shadow-7',
    name: 'Cool Shadow',
    image: 'https://getstream.io/cool-shadow',

    // set publicKey as a extraData property
    extraData: { 'publicKey': keyPair.publicKey },
  ),
  client.devToken('cool-shadow-7').rawValue,
);
```

### Sending Encrypted Messages

Now you will use the `encryptMessage()` function created in the previous steps to encrypt the message.

To do that, you need to make some minor changes to the **StreamMessageInput** widget.

```dart
final receiverJwk = receiver.extraData['publicKey'];

// Generating derivedKey using user's privateKey and receiver's publicKey
final derivedKey = await deriveKey(keyPair.privateKey, receiverJwk);
```

```dart
StreamMessageInput(
  
  ...
  
  preMessageSending: (message) async {
    // Encrypting the message text using derivedKey
    final encryptedMessage = await encryptMessage(message.text, derivedKey);

    // Creating a new message with the encrypted message text
    final newMessage = message.copyWith(text: encryptedMessage);

    return newMessage;
  },
),
```

`preMessageSending` is a parameter that allows your app to process the message before it goes to Stream’s server.
Here, you have used it to encrypt the message before sending it to Stream’s backend.

### Showing Decrypted Messages

Now, it’s time to decrypt the message and present it in a human-readable format to the receiver.

You can customize the **StreamMessageListView** widget to have a custom `messagebuilder`, that can decrypt the message.

```dart
StreamMessageListView(
  ...
  messageBuilder: (context, messageDetails, currentMessages, defaultWidget) {
    // Retrieving the message from details
    final message = messageDetails.message;

    // Decrypting the message text using the derivedKey
    final decryptedMessageFuture = decryptMessage(message.text, derivedKey);
    return FutureBuilder<String>(
      future: decryptedMessageFuture,
      builder: (context, snapshot) {
        if (snapshot.hasError) return Text('Error: ${snapshot.error}');
        if (!snapshot.hasData) return Container();

        // Updating the original message with the decrypted text
        final decryptedMessage = message.copyWith(text: snapshot.data);

        // Returning defaultWidget with updated message
        return defaultWidget.copyWith(
          message: decryptedMessage,
        );
      },
    );
  },
),
```

That's it. That's all you need to implement E2EE in a Stream powered chat app.

For more details, check out our [end-to-end encrypted chat article](https://getstream.io/blog/end-to-end-encrypted-chat-in-flutter/#whats-end-to-end-encryption).
