Implementing End-to-End Encrypted Chat with Virgil E3Kit and Tencent Chat SDK: A Developer's Guide

Tencent RTC-Dev Team
Oct 8, 2024

Scenario Overview

This solution should be implemented by the customer's application based on the E3Kit of virgin security and Chat Sdk. The E3Kit document link: Virtual gild E3Kit|Virtual gild Security.

Learn about the GroupEncryption-End-to-End-Encryption-E3Kit|Virtual Security documentation before reading the following solutions, especially the JWT token(JSON Web Token), create channel/create group, encrypt and decrypt messages.

Open application in virgin security console before using. This product is a paid product. See Pricing|Virgin Security for specific pricing.

The diagram below shows the high-level communication flow:

Products Applied

Chat

Basic Integration Guidelines

1.Chat SDK Initialization.

// 1. Get the `SDKAppID` from the Chat console.
// 2. Initialize the `config` object.
V2TIMSDKConfig config = new V2TIMSDKConfig();
// 3. Specify the log output level.
config.setLogLevel(V2TIMSDKConfig.V2TIM_LOG_INFO);
// 4. Add the `V2TIMSDKListener` event listener. `sdkListener` is the implementation class of `V2TIMSDKListener`. If you don't need to listen to IM SDK events, skip this step.
V2TIMManager.getInstance().addIMSDKListener(sdkListener);
// 5. Initialize the IM SDK. You can call the login API as soon as you call this API.
V2TIMManager.getInstance().initSDK(context, sdkAppID, config);

2.E3Kit initialization, passing in the console configuration information, and generating jwt. Corresponding operation document link: GenerateClientTokens-GetStarted-E3Kit | VirgilSecurity.

The server generates jwttoken and sends it to the client.

// generate jwt
// App Key (you got this Key at the Virgil Dashboard)
String appKeyBase64 = "MC4CAQAwBQYDK2VwBCIEINlK4BhgsijAbNmUqU6us0ZU9MGi+HxdYCA6TdZeHjR4";
byte[] appKeyData = ConvertionUtils.base64ToBytes(appKeyBase64);

// Crypto library imports a key pair
VirgilCrypto crypto = new VirgilCrypto();
VirgilKeyPair keyPair = crypto.importPrivateKey(appKeyData);

// Initialize an access token signer that signs users JWTs
VirgilAccessTokenSigner accessTokenSigner = new VirgilAccessTokenSigner();

// Use your App Credentials you got at the Virgil Dashboard:
String appId = "be00e10e4e1f4bf58f9b4dc85d79c77a";
String appKeyId = "70b447e321f3a0fd";
TimeSpan ttl = TimeSpan.fromTime(1, TimeUnit.HOURS); // 1 hour - JWT's lifetime

// Setup a JWT generator with the required parameters:
JwtGenerator jwtGenerator =
    new JwtGenerator(appId, keyPair.getPrivateKey(), appKeyId, ttl, accessTokenSigner);

// Generate a JWT for a user
// Remember that you must provide each user with a unique JWT.
// Each JWT contains unique user's identity (in this case - Alice).
// Identity can be any value: name, email, some id etc.
String identity = "Alice";
Jwt aliceJwt = jwtGenerator.generateToken(identity);

// As a result you get user's JWT, it looks like this: "eyJraWQiOiI3MGI0NDdlMzIxZjNhMGZkIiwidHlwIjoiSldUIiwiYWxnIjoiVkVEUzUxMiIsImN0eSI6InZpcmdpbC1qd3Q7dj0xIn0.eyJleHAiOjE1MTg2OTg5MTcsImlzcyI6InZpcmdpbC1iZTAwZTEwZTRlMWY0YmY1OGY5YjRkYzg1ZDc5Yzc3YSIsInN1YiI6ImlkZW50aXR5LUFsaWNlIiwiaWF0IjoxNTE4NjEyNTE3fQ.MFEwDQYJYIZIAWUDBAIDBQAEQP4Yo3yjmt8WWJ5mqs3Yrqc_VzG6nBtrW2KIjP-kxiIJL_7Wv0pqty7PDbDoGhkX8CJa6UOdyn3rBWRvMK7p7Ak".
// You can provide users with JWT at registration or authorization steps.
// Send a JWT to client-side.
String jwtString = aliceJwt.stringRepresentation();

The Android client initializes the E3Kit, tokenCallback is the jwttoken returned by the above server, and User1 is the user ID.

// initialization E3Kit
// create EThreeParams with mandatory parameters
// such as identity, tokenCallback and context
EThreeParams params = new EThreeParams("User1",
   tokenCallback,context);
// initialize E3Kit with the EThreeParams
EThree ethree = new EThree(params);

Scenario-specific implementation

User Login

1.Call Chat Sdk login method for account login

String userID = "your user id";
//Generating UserSig
String userSig = "userSig from your server";
V2TIMManager.getInstance().login(userID, userSig, new V2TIMCallback() {
   @Override
   public void onSuccess() {
       Log.i("imsdk", "success");
   }
    @Override
   public void onError(int code, String desc) {
       // The following error codes indicate an expired `userSig`, and you need to generate a new one for login again.
       // 1. ERR_USER_SIG_EXPIRED (6206)
       // 2. ERR_SVR_ACCOUNT_USERSIG_EXPIRED (70001)
       // Note: Do not call the login API in case of other error codes; otherwise, the IM SDK may enter an infinite loop of login.
       Log.i("imsdk", "failure, code:" + code + ", desc:" + desc);
   }
});

2.Call the eThree.register method to register the user to virgilSecurity. The corresponding operation document link: UserAuthentication-E3Kit | VirgilSecurity.

Note:User of im_ ID and user registered on virtilsecurity_ The id should be consistent

Start a one-to-one chat

1.Call the ethree.createRatchetChannel method in the E3Kit to create a one to one session (User1 and User2), and the corresponding operation document link: https://developer.virgilsecurity.com/docs/e3kit/end-to-end-encryption/double-ratchet/#create-channel

User1 creates a channel with User2

// create one-to-one channel
ethree.createRatchetChannel(users.get("User2"))
      .addCallback(new OnResultListener<RatchetChannel>() {
          @Override public void onSuccess(RatchetChannel ratchetChannel) {
              // Channel created and saved locally!
          }

          @Override public void onError(@NotNull Throwable throwable) {
              // Error handling
          }
      });

User2 can join the channel

// join channel
ethree.joinRatchetChannel(users.get("User1"))
      .addCallback(new OnResultListener<RatchetChannel>() {
    @Override public void onSuccess(RatchetChannel ratchetChannel) {
        // Channel joined and saved locally!
    }

    @Override public void onError(@NotNull Throwable throwable) {
        // Error handling
    }
});

One-to-one chat message encryption and decryption

1.Use the channel created above to encrypt messages and link documents:https://developer.virgilsecurity.com/docs/e3kit/end-to-end-encryption/double-ratchet/?#encrypt-and-decrypt-messages.

// one-to-one chat message encryption
// prepare a message
String messageToEncrypt = "Hello, User2!";

String encrypted = channel.encrypt(messageToEncrypt);

2.The encrypted message content is sent to Chat Sdk and sent with a customized message

// `msgID` returned by the API for on-demand use
String msgID = V2TIMManager.getInstance().sendC2CCustomMessage("virgil encrypted msg", "receiver_userID", new V2TIMValueCallback<V2TIMMessage>() {
@Override
public void onSuccess(V2TIMMessage message) {
    // The one-to-one text message sent successfully
}
    @Override
public void onError(int code, String desc) {
    // Failed to send the one-to-one text message
}
});

3.After the peer receiving the customized message

// Set the event listener
V2TIMManager.getInstance().addSimpleMsgListener(simpleMsgListener);

/**
* Receive the custom one-to-one message
* @param msgID Message ID
* @param sender Sender information
* @param customData The sent content
*/
public void onRecvC2CCustomMessage(String msgID, V2TIMUserInfo sender, byte[] customData) {
Log.i("onRecvC2CCustomMessage", "msgID:" + msgID + ", from:" + sender.getNickName() + ", content:" + new String(customData));
//call E3Kit to decrypt msg
}

4.the peer calls E3Kit to decrypt and render it, as follows:

// Decrypt message
String decrypted = channel.decrypt(encrypted);

Start a channel(group)

1.Call the ethree.createGroup to create group,document link:https://developer.virgilsecurity.com/docs/e3kit/end-to-end-encryption/group-chat/?#create-group-chat.

// create group
ethree.createGroup(groupId, users).addCallback(new OnResultListener<Group>() {
    @Override public void onSuccess(Group group) {
        // Group created and saved locally!
    }

    @Override public void onError(@NotNull Throwable throwable) {
        // Error handling
    }
});

2.If you need to add group members, the code below is as follows. You can call the remove method to delete a group member. Document link:https://developer.virgilsecurity.com/docs/e3kit/end-to-end-encryption/group-chat/?#add-new-participant.

// add group member
group.add(users.get("Den")).addCallback(new OnCompleteListener() {
    @Override public void onSuccess() {
        // Den was added!
    }

    @Override public void onError(@NotNull Throwable throwable) {
        // Error handling
    }
});

3.Call createGroup of Chat Sdk to create a group, joinGroup or inviteUserToGroup to add group members.

V2TIMManager.getInstance().createGroup(V2TIMManager.GROUP_TYPE_WORK, null, "groupA", new V2TIMValueCallback<String>() {
 @Override
 public void onSuccess(String s) {
     // Group created successfully
 }
  @Override
 public void onError(int code, String desc) {
     // Failed to create the group
 }
});
// Listen for the group creation notification
V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {
 @Override
 public void onGroupCreated(String groupID) {
     // A group was created. `groupID` is the ID of the created group.
 }
});
// Invite the `userA` user to join the `groupA` group
List<String> userIDList = new ArrayList<>();
userIDList.add("userA");
V2TIMManager.getGroupManager().inviteUserToGroup("groupA", userIDList, new V2TIMValueCallback<List<V2TIMGroupMemberOperationResult>>() {
 @Override
 public void onSuccess(List<V2TIMGroupMemberOperationResult> v2TIMGroupMemberOperationResults) {
     // Invited the user to the group successfully
 }
  @Override
 public void onError(int code, String desc) {
     // Failed to invite the user to the group
 }
});
// Listen for the group invitation event
V2TIMManager.getInstance().addGroupListener(new V2TIMGroupListener() {
 @Override
 public void onMemberInvited(String groupID, V2TIMGroupMemberInfo opUser, List<V2TIMGroupMemberInfo> memberList) {
     // A user was invited to the group. This callback can contain some UI tips.
 }
});

Group chat message encryption and decryption

1.Use the group created above to encrypt messages and link documents:https://developer.virgilsecurity.com/docs/e3kit/end-to-end-encryption/group-chat/?#encrypt-and-decrypt-messages.

//Group message encryption
// prepare a message
String messageToEncrypt = "Hello, Bob and Carol!";

String encrypted = group.encrypt(messageToEncrypt);

2.The encrypted message content is sent to tencent Chat Sdk and sent with a customized message

String msgID = V2TIMManager.getInstance().sendGroupCustomMessage("virgil encrypted msg ".getBytes(), "groupID", V2TIMMessage.V2TIM_PRIORITY_NORMAL, new V2TIMValueCallback<V2TIMMessage>() {
@Override
public void onSuccess(V2TIMMessage message) {
    // The custom group message sent successfully
}

3.After the peer tencent cloud Chat Sdk receiving the customized message,

// Set the event listener
V2TIMManager.getInstance().addSimpleMsgListener(simpleMsgListener);

/**
* Receive the custom group message
* @param msgID Message ID
* @param groupID Group ID
* @param sender The group member information of the sender
* @param customData The sent content
*/
public void onRecvGroupCustomMessage(String msgID, String groupID, V2TIMGroupMemberInfo sender, byte[] customData) {
Log.i("onRecvGroupCustomMessage", "msgID:" + msgID + ", groupID:" + groupID + ", from:" + sender.getNickName() + ", content:" + new String(customData));
//call E3Kit to decrypt msg
}

4.The peer decrypt and render it, as follows:

//Group message decryption
String decrypted = group.decrypt(encrypted, users.get("Alice"));

Note: After using this end-to-end encryption scheme, the local chat record search function of imsdk will not be available.

Order Now

Click here to quickly go to the purchase page to order.