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 isDatabaseReference 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.
}
});
try {
SecretKey messageKey = EncryptionHelper.deriveChatKey(masterkey, message_id);
encrypted_message = EncryptionHelper.encryptMessage(messageKey, message);
} catch (Exception e){
showMessage(e.toString());
}
try {
SecretKey messageKey = EncryptionHelper.deriveChatKey(masterkey, message_id);
message = EncryptionHelper.decryptMessage(messageKey, encrypted_message);
} catch (Exception e){
showMessage(e.toString());
}
Good project
ReplyDelete