End-to-end encryption Flutter + Firebase real-time database

1.3k views Asked by At

I am making a chat application where:

  1. A user can be in a group with multiple other users.
  2. All messages must be encrypted with end-to-end

I am using firebase-realtime-database to store data.


What I have

  • Randomly generated secret key for every user

    String generateEncryptionKey(int len) {
        var r = Random();
        String randomString = String.fromCharCodes(List.generate(len,
                                    (index) => r.nextInt(33) + 89));
        return randomString;
    }
    

This is probably wrong. I would need clarification if I need to get a derived key from the user's password or if this is sufficient.


What I need

  • Alice sends a message: I need it to be encrypted with a key that is also accessible by any other participant in the group (shared-secret key)
  • Bob wants to read this message: I need to decrypt this with the shared-secret key

The shared-secret key shares something in common with every personal key, right? So the messages encrypted by any of the participants can be decrypted by any other.

How can I generate the shared-secret key?


Code Blocks Needed

generateKeyPair() {
    // Generate a private - public keypair for each user
    // ...somehow used to make the `shared-secret key`?
}

generateSharedSecretKey() {
    // Saved to database as the group's shared secret key.
    // only participants can use it to decrypt messages
}

sendMessage() {
    // 1. Encrypt the message (**code needed**)...using
    //    which combination of keys?
    // 2. Save to database (I already handle this)
}

receiveMessage() {
    // 1. Read from database (I already handle this)
    // 2. Decrypt the message (**code needed**)...using
    //    which combination of keys?
}

Considerations

  • Multiple users (so many keys)
  • It doesn't need to be super secure (just the bare minimum is enough)
  • A user can have an account on multiple devices

I have read about the Diffie–Hellman key exchange, key pairs, etc. But I don't really understand all the concepts as I am quite new.

I would need:

  1. Clarification on basic end-to-end encryption concepts (in case something in my explanation was wrong)
  2. Simple code samples of every key generation step
  3. The code blocks have to work no matter which user is sending a message or receiving. That dynamism is what confuses me. How can you encrypt something taking into account that any of 50 users can read it?

I just want encryption of simple data (strings) with multiple users through a database. Is there is an easier way than end-to-end?

3

There are 3 answers

4
Koushik Boinepally On BEST ANSWER

Group Chat

First the group admin (or whoever creates the group) must generate a single key for the group. This uses symmetric encryption where a single key is used for decryption and encryption. This key should be shared with all the participants.

enter image description here

When a participant wants to send the message, it is encrypted using this key and sent to the server, which simply retransmits it to the participants inbox.

enter image description here

Since user 2 and 3 already have the shared secret key, they decrypt it client side. There are many choices for this kind of encryption, you can google any "Symmetric Encryption Algorithm" (Eg: AES) and pick one that is available on pub.dev.

Things to Note:

  1. For server side retransmitting, you could use a simple firebase cloud function or eliminate the server entirely (not recommended) by allowing the client to send the messages to each participant

EDIT: If you want to encrypt the transport of the Shared Secret Key, you could leverage your existing 1 to 1 architecture and the group admin can send the key to each of the participants by encrypting using the participants public key (which of course they will decrypt on their end). As long as you handle the sendGroupSharedSecretKeyToParticipants() properly on senders side and onReceiveGroupSharedSecretKey() on receivers end, you should be fine. This may complicate things a tad bit (handling of sharing the key differently from a typical chat message) but this would be a simple way to do it.

1 to 1 Chat

Each user can have a private key and a public key, messages are sent by encrypting using the receivers public key and the receiver will decrypt it using their own private key, pretty basic stuff which you've already figured out or refer to @Jabbar's answer https://stackoverflow.com/a/74809596/4481095

4
user16930239 On

I think if you fully understand end-to-end encryption you will easily do this.

Here is a diagram to explain public/private keys in simple terms:

Enter image description here

So for example User A wants to receive a secure message from User B:

  1. User A will generate a public/private key pair. The private key is kept locally with User A, and no one else has access to this.
  2. User A will share only the public key when anyone wants to send User A a message.
  3. Public key is like an open suitcase with a secret code (private key)
  4. User B wants to send a secure message to User A
  5. User B will use the public key to encrypt the message (put the message inside the suitcase and close it) and send it back to User A
  6. The message will need the private key to be decrypted
  7. Only User A has the Private key, so User A is the only one who could see the message. Not even User B can decrypt the message after encryption
0
Shubhanshu Kashiva On

You can also try with the crypto Flutter package, so with the help of this package you can create a key which will satisfy your requirement and you can manage for each and every user. You can also check out the below package which is published by the dart.dev team.

Here is a code snippet from the above package also just for convenience.

import 'dart:convert';
import 'package:crypto/crypto.dart';

void main() {
  var key = utf8.encode('p@ssw0rd');
  var bytes = utf8.encode("foobar");

  var hmacSha256 = Hmac(sha256, key); // HMAC-SHA256
  var digest = hmacSha256.convert(bytes);

  print("HMAC digest as bytes: ${digest.bytes}");
  print("HMAC digest as hex string: $digest");
}