Add encryption in firebase group chat

 Brief summary of this post:

1. The user enters a passphrase which is unique to the group chat and has to be shared to the user for granting him access.

2. The passphrase is saved in SharedPreferences.

3. A master key is generated using the passphrase.

4. While sending message, a secret key is generated using the master key and message ID. The message is encrypted using this secret key.

5. On retrieving message, it is decrypted using a secret key generated using master key and message ID.


Steps:

1. In Button to enter the group chat page, add a dialog box (dialog2) with EditText (dialog_text1) where user can enter the passphrase. On entering the passphrase it is saved in SharedPreferences (sp:sp) and user moves to group chat activity using intent component.



Code to add an EditText to Dialog component:

final EditText dialog_text1 = new EditText(AdminpageActivity.this);

dialog_text1.setLayoutParams(linear2.getLayoutParams());

dialog2.setView(dialog_text1);

Code to get text from EditText:

dialog_text1.getText().toString()

2. Add another button which can be used to reenter passphrase, in case user has entered the wrong passphrase.



3. In local library manager, add the following library and select it:

androidx.security:security-crypto:1.1.0-alpha06

4. In Java/Kotlin manager, add a new Java class file EncryptionHelper.java and put following codes in it. Make sure to keep your own package name at the top.


package com.my.dmchat;

import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;
import java.util.Arrays;

import android.content.SharedPreferences;
import android.content.Context;
import androidx.security.crypto.MasterKey;
import androidx.security.crypto.EncryptedFile;
import androidx.security.crypto.EncryptedSharedPreferences;

import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyStore;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.Mac;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.spec.KeySpec;
import javax.crypto.spec.SecretKeySpec;

public class EncryptionHelper {

    /**
     * Derives a Master Key from a given passphrase using PBKDF2.
     * @param passphrase The user's passphrase.
     * @return SecretKey The derived master key.
     * @throws Exception if key derivation fails.
     */
    public static SecretKey deriveMasterKey(String passphrase) throws Exception {
        byte[] salt = "SomeFixedSaltValue".getBytes(StandardCharsets.UTF_8); // Can be static or unique per chat
        int iterations = 100000;
        int keyLength = 256;

        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), salt, iterations, keyLength);
        SecretKey tmpKey = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");

        return tmpKey;
    }

    /**
     * Derives a unique chat-specific encryption key from the master key using HMAC.
     * @param masterKey The master key.
     * @param chatID The chat identifier.
     * @return SecretKey The derived chat key.
     * @throws Exception if key derivation fails.
     */
    public static SecretKey deriveChatKey(SecretKey masterKey, String chatID) throws Exception {
        Mac hmac = Mac.getInstance("HmacSHA256");
        hmac.init(masterKey);
        byte[] secretKeyBytes = hmac.doFinal(chatID.getBytes(StandardCharsets.UTF_8));
        
        return new SecretKeySpec(Arrays.copyOf(secretKeyBytes, 16), "AES"); // 128-bit AES key
    }

    /**
     * Encrypts a message using AES-GCM encryption.
     * @param chatKey The AES key used for encryption.
     * @param plaintext The message to be encrypted.
     * @return Encrypted message as a Base64 string.
     * @throws Exception if encryption fails.
     */
    public static String encryptMessage(SecretKey chatKey, String plaintext) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, chatKey);
        byte[] iv = cipher.getIV();
        byte[] encryptedData = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
    
        byte[] combined = new byte[iv.length + encryptedData.length];
        System.arraycopy(iv, 0, combined, 0, iv.length);
        System.arraycopy(encryptedData, 0, combined, iv.length, encryptedData.length);
    
        return Base64.encodeToString(combined, Base64.DEFAULT);
    }

    /**
     * Decrypts an AES-GCM encrypted message.
     * @param chatKey The AES key used for decryption.
     * @param encryptedData The Base64 encoded encrypted message.
     * @return Decrypted plaintext message.
     * @throws Exception if decryption fails.
     */
    public static String decryptMessage(SecretKey chatKey, String encryptedData) throws Exception {
        byte[] decodedData = Base64.decode(encryptedData, Base64.DEFAULT);
        byte[] iv = new byte[12]; // GCM IV length is 12 bytes
        System.arraycopy(decodedData, 0, iv, 0, iv.length);
    
        byte[] encryptedBytes = new byte[decodedData.length - iv.length];
        System.arraycopy(decodedData, iv.length, encryptedBytes, 0, encryptedBytes.length);
    
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec spec = new GCMParameterSpec(128, iv);
        cipher.init(Cipher.DECRYPT_MODE, chatKey, spec);
    
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }
}

5. In groupchat.xml add a ListView listview1, an EditText edittext1 and a Button send.



6. Create a Custom View groupchat_item.xml and add items as shown in image below. Select it as Custom View of listview1.



7. In GroupchatActivity, add following components:

FirebaseDb component group_chat:groupchat, Calendar component cal, Dialog components dialog and dialog2, SharedPreferences component sp:sp, and FirebaseAuth component fauth.

8. Add following variables:

- String variables message_id, message, encrypted_message, date, passphrase, uid and username.

- Map variable map.

- List Map variable maplist.

9. Add import event and put following imports:

import javax.crypto.SecretKey;

10. Create a more block Declared and put codes to declare masterkey in it:

}
SecretKey masterkey;
{

11. In onCreate, get passphrase from shared preferences and use it to generate masterkey as shown in image below:


The code to generate masterkey is:

try {
masterkey = EncryptionHelper.deriveMasterKey(passphrase);

} catch (Exception e){
showMessage(e.toString());
}

12. Create a more block addUsername(username) fromMap(Map:map) toList (List Map:maplist) and put blocks as shown below.


13. In group_chat onChildAdded event put blocks as shown in image below.

Here the code to get username from uid is

DatabaseReference usersRef = FirebaseDatabase.getInstance().getReference("users").child(uid);
 usersRef.addListenerForSingleValueEvent(new ValueEventListener() {

@Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.exists()) {
username= snapshot.child("username").getValue(String.class);

// Put more block to add Username to maplist and refresh ListView.

}
}

@Override
public void onCancelled(DatabaseError error) {
username = uid;

// Put more block to add Username to maplist and refresh ListView.

}

});

14. In send button onClick event, put following blocks:

Here the code to derive Secret key from message_id and encrypt the message is:
try {
SecretKey messageKey = EncryptionHelper.deriveChatKey(masterkey, message_id);

encrypted_message = EncryptionHelper.encryptMessage(messageKey, message);

} catch (Exception e){
showMessage(e.toString());
}


15. In listview1 onBindCustomView, use following blocks to display the message and username.


The code to decrypt message using secret key derived from master key and message ID is following:
try {
SecretKey messageKey = EncryptionHelper.deriveChatKey(masterkey, message_id);
 
message = EncryptionHelper.decryptMessage(messageKey, encrypted_message);

} catch (Exception e){
showMessage(e.toString());
}

Use following blocks to display the date and time.


16. In listview1 itemLongClicked event show a dialog to delete item and delete item from firebase.



17. In group_chat onChildRemoved event put codes as shown in image below:


For more insight watch video on youtube.



Comments

Post a Comment

Popular posts from this blog

Simple car racing android game in Sketchware

Creating a Drawing View in Sketchware

How to enable upload from webview in Sketchware?

Get frames in a video in Sketchware

List of Calendar Format symbols valid in Sketchware