Security & Privacy
Encrypted Chat
52 min
how to make a gdpr compliant chat app introduction ahoy back4app community! this is a guest tutorial from the team at virgil security, inc https //virgilsecurity com/?utm source=back4app\&utm medium=blog\&utm campaign=e2eechat we’re the crypto tech behind twilio’s end to end encrypted messaging https //www twilio com/blog/2016/05/introducing end to end encryption for twilio ip messaging with virgil security html our friends @ back4app asked us to show you how to build an end to end encrypted chat app on top of back4app in this post, we’ll walk you through the steps to make a simple back4app android messenger app end to end encrypted! are you ready? ps if you don’t care about the details, simply skip to the end of the post and download the final product what is end to end encryption? first, let’s start with a quick refresher of what e2ee (end to end encryption) is and how it works e2ee is simple when you type in a chat message, it gets encrypted on your mobile device (or in your browser) and gets decrypted only when your chat partner receives it and wants to display it in chat window the message remains encrypted while it travels over wi fi and the internet, through the cloud / web server, into a database, and on the way back to your chat partner in other words, none of the networks or servers have a clue of what the two of you are chatting about what’s difficult in end to end encryption is the task of managing the encryption keys in a way that only the users involved in the chat can access them and nobody else and when i write “nobody else”, i really mean it even insiders of your cloud provider or even you, the developer, are out; no accidental mistakes https //techcrunch com/2017/11/29/meet the man who deactivated trumps twitter account/ or legally enforced peeking are possible writing crypto, especially for multiple platforms is hard generating true random numbers, picking the right algorithms, and choosing the right encryption modes are just a few examples that make most developers wave their hands in the air and end up just not doing it this blog post will show you how to ignore all these annoying details and quickly and simply end to end encrypt using virgil’s sdk for an intro, this is how we’ll upgrade back4app’s messenger app to be end to end encrypted during sign up we’ll generate the individual private & public keys for new users (remember the recipient’s public key encrypts messages and the matching recipient’s private key decrypts them) before sending messages, you’ll encrypt chat messages with the recipient’s public key after receiving messages, you’ll decrypt chat messages with the recipient’s private key we’ll publish the users’ public keys to virgil’s cards service so that chat users are able to look up each other and able to encrypt messages for each other the private keys will stay on the user devices keep it simple this is the simplest possible implementation of e2ee chat and it works perfectly for simple chat apps between 2 users where conversations are short lived and it’s okay to lose the message history if a device is lost with the private key on it ok, enough talking! let’s get down to coding we’ll start by guiding you through the android app’s setup, then, we’ll add some code to make the app end to end encrypted prerequisites to complete this tutorial, you need android studio an app created at back4app follow the create new app tutorial to learn how to create an app at back4app sign up for a virgil security account (we’ll create the app later) let’s set up the “clean” back4app messenger app 1 set up your app server let’s start with deploying the cloud function for this, you will need to find main js main js and package json package json in scripts scripts directory; open main js main js with your favorite editor 1 1) get back4app credentials open dashboard dashboard of your app > app settings app settings > security & keys security & keys in main js main js , replace parse app id parse app id with your application id application id and parse rest api key parse rest api key with your rest api key rest api key 1 2) get virgil credentials create an application at virgil dashboard https //dashboard virgilsecurity com/ open your new virgil application, navigate to e3kit section and and generate a env env file under the e3kit section in the left side bar copy the values of app id app id , app key app key , and app key id app key id from the env env file replace the copied values in your main js main js file in the corresponding fields ( main js main js of scripts scripts directory) 1 3) deploy cloud code function open back4app “dashboard” of your app > “core” > cloud code functions; click +add and select your main js and package json (from scripts directory), after that move both of them to the cloud folder; click deploy 2 start clean back4app kotlin demo app don’t forget to set up back4app cloud code function first it is a mandatory part of this demo after this, go through the following steps 2 1) import project in android studio open android studio > file file > new new > project from version control project from version control > git git git repository url https //github com/virgilsecurity/chat back4app android check out the clean chat kt clean chat kt branch important! select “project” type of file tree it will be used all through the tutorial 2 2) setup back4app credentials in project open back4app “dashboard” of your app > “app settings” > “security & keys”; go to /app/src/main/res/values/strings xml /app/src/main/res/values/strings xml file in your android project and replace your back4app app id your back4app app id with your application id application id and your back4app client key your back4app client key with your client key client key 2 3) setup db open back4app “dashboard” > “core” > “database browser” > “create a class” and create classes of custom custom type named message message and chatthread chatthread ; 2 4) setup live query go back to your back4app account https //dashboard back4app com/apps/#!/admin press the server settings server settings button on your application find the “web hosting and live query” block open the live query settings and check the activate hosting activate hosting option choose a name for your subdomain to activate live query for the 2 classes you created message message and chatthread chatthread copy your new subdomain name and click the save button return to /app/src/main/res/values/strings xml /app/src/main/res/values/strings xml and paste “subdomain name” you have entered above into the back4app live query url back4app live query url instead of “yoursubdomainname” after these steps you will be able to hit the run button in android studio and get the sample to work use emulator or real device to test it out 3 run the clean demo to see the result of running the clean version of the demo, you’ll need to sign up 2 users; start a conversation between them and send a couple of messages; 3\ open back4app “dashboard” > “core” > “database browser” > “message” if it all worked out, you should see the chat messenger app popping up register two users and send a few messages to each other you should see new data showing up in the message message class note that you can see on the server what your users are chatting about next close your chat interface and move on to the next step – adding e2ee encryption now, let’s end to end encrypt those messages! by the end of this part, this is how your chat messages will look like on the server can you spot the difference? how do we get there? obviously, we need to implement end to end encryption, which means that our app needs to generate the private & public key pair as part of signup store the private key in the key storage on user’s device publish the public key in virgil’s card service as a “virgil card” for other users to download & encrypt messages with it encrypt messages with public key and sign with private key; decrypt messages with private key and verify with public key for this, we will need to add e3kit to our clean demo application and some more code to implement all that was described above but before we begin, let’s clear two important terms for you what’s a virgil card and a private key? virgil card virgil сards carry the users’ private keys virgil cards are published to virgil’s cards service (imagine this service is like a telephone book) for other users to retrieve them alice needs to retrieve bob’s public key in order to encrypt a message for bob using that key private key a private part of the encryption key remember, private keys can decrypt data that was encrypted using the matching public key 1 add e3kit to the clean e3kit back4app kotlin demo in the app level ( module app module app ) gradle at /app/build gradle /app/build gradle add (but don’t sync gradle scripts just yet) add the following to the end of your project level /build gradle /build gradle ( project chat back4app android project chat back4app android ) now you can synchronize gradle scripts; update your appvirgil appvirgil class by adding new fields press alt+ enter alt+ enter to import the necessary libraries into the class 2 authenticate users with back4app cloud code in /virgilsecurity/virgilback4app/model/ /virgilsecurity/virgilback4app/model/ directory, create data classes authenticateresponse authenticateresponse and virgiljwtresponse virgiljwtresponse that represent responses from cloud code functions in /virgilsecurity/virgilback4app/util/ /virgilsecurity/virgilback4app/util/ create authrx authrx object that implements calls to cloud code functions (don’t forget to import all the necessary libraries afterwards) 1 object authrx { 2 3 / 4 you can call it only after successful \[authenticate] 5 / 6 fun virgiljwt(sessiontoken string) = single create\<string> { emitter > 7 val requestparams = mutablemapof\<string, string>() apply { 8 put("sessiontoken", sessiontoken) 9 } 10 11 parsecloud callfunctioninbackground\<map\<string, any>>( 12 key virgil jwt, 13 requestparams 14 ) { virgiljwt, exception > 15 if (exception == null) 16 emitter onsuccess(virgiljwt\[key token] tostring()) 17 else 18 emitter onerror(exception) 19 20 } 21 } 22 23 private const val key virgil jwt = "virgil jwt" 24 private const val key token = "token" 25 } 3 store virgil jwt locally virgil token received from cloud code functions needs to be stored locally let’s update preferences preferences class in /virgilsecurity/virgilback4app/util/ /virgilsecurity/virgilback4app/util/ define constant add functions setvirgiltoken setvirgiltoken , virgiltoken virgiltoken and clearvirgiltoken clearvirgiltoken kotlin fun setvirgiltoken(virgiltoken string) { with(sharedpreferences edit()) { putstring(key virgil token, virgiltoken) apply() } } fun virgiltoken() string? { with(sharedpreferences) { return getstring(key virgil token, null) } } fun clearvirgiltoken() { with(sharedpreferences edit()) { remove(key virgil token) apply() } } 1 virgil token should be reset on logout let's add `preferences instance(this) clearvirgiltoken()` line into `initdrawer` function of `threadslistactivity` class (in ` /virgilsecurity/virgilback4app/chat/contactslist/`) 2 kotlin 3 private fun initdrawer() { 4 5 nvnavigation setnavigationitemselectedlistener { item > 6 r id itemlogout > { 7 dldrawer closedrawer(gravitycompat start) 8 presenter disposeall() 9 showbaseloading(true) 10 // new code >> 11 preferences instance(this) clearvirgiltoken() 12 // << new code 13 14 } 15 } 16 } 4 modify user registration e3kit takes care about your private and public keys to generate them during the registering process, we’ll need to do the following in /virgilsecurity/virgilback4app/util/ /virgilsecurity/virgilback4app/util/ create rxethree rxethree class now, add initethree initethree function that initializes e3kit instance in rxethree rxethree class 1 import com virgilsecurity android common model ethreeparams 2 import com virgilsecurity android ethree interaction ethree 3 4 5 fun initethree(identity string, verifyprivatekey boolean = false) 6single\<ethree> = single create { e > 6 val params = ethreeparams(identity, {preferences virgiltoken()!!}, context) 7 val ethree = ethree(params) 8 if (verifyprivatekey) { 9 if (ethree haslocalprivatekey()) { 10 e onsuccess(ethree) 11 } else { 12 e onerror(keyentrynotfoundexception()) 13 } 14 } else { 15 e onsuccess(ethree) 16 } 17 } add registerethree registerethree function that registers a new user to rxethree rxethree class e3kit generates a key pair during a sign up the generated private key then is stored in local storage, and public key is published to virgil services as a virgil card 1 import com android virgilsecurity virgilback4app appvirgil 2 import com virgilsecurity common callback oncompletelistener 3 import io reactivex completable 4 5 6 fun registerethree() completable = completable create { e > 7 appvirgil ethree register() addcallback(object oncompletelistener { 8 override fun onerror(throwable throwable) { 9 e onerror(throwable) 10 } 11 12 override fun onsuccess() { 13 e oncomplete() 14 } 15 16 }) 17 } let’s make some updates to loginpresenter loginpresenter class (in /virgilsecurity/virgilback4app/auth/) /virgilsecurity/virgilback4app/auth/) add rxethree rxethree field 1 private val rxethree = rxethree(context) update the requestsignup requestsignup function to run registration with e3kit 1 fun requestsignup(identity string, onsuccess () > unit, onerror (throwable) > unit) { 2 val password = generatepassword(identity tobytearray()) 3 4 val disposable = rxparse signup(identity, password) 5 subscribeon(schedulers io()) 6 observeon(schedulers io()) 7 // new code >> 8 tosingle { parseuser getcurrentuser() } 9 flatmap { authrx virgiljwt(it sessiontoken) } 10 map { preferences setvirgiltoken(it) } 11 flatmap { rxethree initethree(identity) } 12 map { appvirgil ethree = it } 13 flatmap { rxethree registerethree() tosingle { unit } } 14 // << new code 15 observeon(androidschedulers mainthread()) 16 // updated code >> 17 subscribeby( 18 onsuccess = { 19 onsuccess() 20 }, 21 onerror = { 22 onerror(it) 23 } 24 ) 25 // << updated code 26 27 compositedisposable += disposable 28 } 5 modify sign in functions now, let’s make changes to requestsignin requestsignin method of loginpresenter loginpresenter class (in /virgilsecurity/virgilback4app/auth/) /virgilsecurity/virgilback4app/auth/) 1 fun requestsignin(identity string, 2 onsuccess () > unit, 3 onerror (throwable) > unit) { 4 5 val password = generatepassword(identity tobytearray()) 6 7 val disposable = rxparse login(identity, password) 8 subscribeon(schedulers io()) 9 observeon(schedulers io()) 10 // new code >> 11 flatmap { authrx virgiljwt(it sessiontoken) } 12 map { preferences setvirgiltoken(it) } 13 flatmap { rxethree initethree(identity, true) } 14 map { appvirgil ethree = it } 15 // << new code 16 observeon(androidschedulers mainthread()) 17 // updated code >> 18 subscribeby( 19 onsuccess = { 20 onsuccess() 21 }, 22 onerror = { 23 onerror(it) 24 } 25 ) 26 // << updated code 27 28 compositedisposable += disposable 29 } 6 get the list of existing chat next, add functions that handle e3kit initialization into threadslistfragment threadslistfragment class (in /virgilsecurity/virgilback4app/chat/contactslist/) /virgilsecurity/virgilback4app/chat/contactslist/) 1 private fun oninitethreesuccess() { 2 presenter requestthreads(parseuser getcurrentuser(), 3 20, 4 page, 5 const tablenames created at criteria, 6 ongetthreadssuccess, 7 ongetthreadserror) 8 } 9 10 private fun oninitethreeerror(throwable throwable) { 11 showprogress(false) 12 if (adapter itemcount == 0) 13 tverror visibility = view\ visible 14 15 utils toast(activity, utils resolveerror(throwable)) 16 } update postcreateinit postcreateinit function to initialize e3kit 1 override fun postcreateinit() { 2 3 presenter = threadslistfragmentpresenter(activity) 4 5 showprogress(true) 6 // updated code >> 7 if (appvirgil isethreeinitialized()) { 8 presenter requestthreads(parseuser getcurrentuser(), 9 20, 10 page, 11 const tablenames created at criteria, 12 ongetthreadssuccess, 13 ongetthreadserror) 14 } else { 15 presenter requestethreeinit(parseuser getcurrentuser(), oninitethreesuccess, oninitethreeerror) 16 } 17 // << updated code 18 } and add the following code into threadslistfragmentpresenter threadslistfragmentpresenter class in virgilsecurity virgilback4app chat contactslist/ virgilsecurity virgilback4app chat contactslist/ add field 1 private val rxethree = rxethree(context) and function 1 fun requestethreeinit(currentuser parseuser, onsuccess () > unit, onerror (throwable) > unit) { 2 val disposable = rxethree initethree(currentuser username) 3 subscribeon(schedulers io()) 4 observeon(androidschedulers mainthread()) 5 subscribeby( 6 onsuccess = { 7 appvirgil ethree = it 8 onsuccess() 9 }, 10 onerror = { 11 onerror(it) 12 } 13 ) 14 15 compositedisposable += disposable 16 } at this point we are able to sign up/sign in a user and create a new chat with other user now let’s add encryption for our messages 7 message encryption and decryption let’s add findcard findcard function to rxethree rxethree class (in /virgilsecurity/virgilback4app/util/ /virgilsecurity/virgilback4app/util/ ) that will help us to get the latest virgil card by user name 1 fun findcard(identity string) single\<card> = single create { e > 2 appvirgil ethree finduser(identity) addcallback(object onresultlistener\<card> { 3 override fun onerror(throwable throwable) { 4 e onerror(throwable) 5 } 6 7 override fun onsuccess(result card) { 8 e onsuccess(result) 9 } 10 11 }) 12 } when a chat is opened, we should obtain virgil card of the recipient edit postcreateinit postcreateinit of chatthreadfragment chatthreadfragment class (in /virgilsecurity/virgilback4app/chat/thread/ /virgilsecurity/virgilback4app/chat/thread/ ) by replacing 1 presenter requestmessages(thread, 2 50, 3 page, 4 const tablenames created at criteria, 5 ongetmessagessuccess, 6 ongetmessageserror) code with a new one 1 presenter requestcard(recipientid, 2 ongetcardsuccess, 3 ongetcarderror) and add two functions 1 private fun ongetcardsuccess(card card) { 2 showprogress(false) 3 adapter interlocutorcard = card 4 presenter requestmessages(thread, 5 50, 6 page, 7 const tablenames created at criteria, 8 ongetmessagessuccess, 9 ongetmessageserror) 10 } 11 12 private fun ongetcarderror(t throwable) { 13 if (t is virgilcardisnotfoundexception || t is virgilcardserviceexception) { 14 utils toast(this, 15 "virgil card is not found \nyou can not chat with user without virgil card") 16 activity onbackpressed() 17 } 18 showprogress(false) 19 srlrefresh isrefreshing = false 20 locksendui(lock = false, lockinput = false) 21 22 utils toast(this, utils resolveerror(t)) 23 } now let’s change chatthreadpresenter chatthreadpresenter add fields 1 private val ethree = appvirgil ethree 2 private lateinit var usercard card 3 private val rxethree = rxethree(context) add a function that obtains a virgil card of the recipient 1 fun requestcard(identity string, 2 onsuccess (card) > unit, 3 onerror (throwable) > unit) { 4 5 val disposable = rxethree findcard(identity) 6 subscribeon(schedulers io()) 7 observeon(androidschedulers mainthread()) 8 subscribeby( 9 onsuccess = { 10 usercard = it 11 onsuccess(it) 12 }, 13 onerror = { 14 onerror(it) 15 } 16 ) 17 18 compositedisposable += disposable 19 } add encryption of outcoming messages in requestsendmessage requestsendmessage function 1 fun requestsendmessage(text string, 2 thread chatthread, 3 onsuccess () > unit, 4 onerror (throwable) > unit) { 5 6 val encryptedtext = ethree authencrypt(text, usercard) 7 val disposable = rxparse sendmessage(encryptedtext, thread) 8 9 } add decryption of all the incoming messages into chatthreadrvadapter chatthreadrvadapter class (in /virgilsecurity/virgilback4app/chat/thread/) /virgilsecurity/virgilback4app/chat/thread/) add fields 1 private var ethree ethree = appvirgil ethree 2 lateinit var interlocutorcard card implement message decryption in onbindviewholder onbindviewholder function 1 override fun onbindviewholder(viewholder recyclerview\ viewholder, position int) { 2 when (viewholder) { 3 is holdermessageme > { 4 val decryptedtext = ethree authdecrypt(items\[position] body) 5 viewholder bind(decryptedtext) 6 } 7 is holdermessageyou > { 8 val decryptedtext = ethree authdecrypt(items\[position] body, interlocutorcard) 9 viewholder bind(decryptedtext) 10 } 11 } 12 } 8 run the complete end to end encrypted demo now to see the result of our fully end to end encrypted demo, go through these steps again log out the previous user sign up 2 new users; start a conversation between them and send a couple of messages; open back4app “dashboard” > “core” > “database browser” > “message” important! you have to log out the current user and register two new users, after that you can start e2ee chat with those two new users the reason is that your first two users have no virgil card virgil card ’s, so you can not use encrypt\decrypt for them { blockquote tip} done! now you can see that users’ messages are encrypted and can only be accessed in app by users themselves hipaa & gdpr compliance end to end encryption is a way to meet the technical requirements for hipaa (the united states’ health insurance portability and accountability act of 1996) & gdpr (the european union’s general data protection regulation) if you need more details, sign up for a free virgil account https //developer virgilsecurity com/account/signup?utm source=back4app\&utm medium=blog\&utm campaign=e2eechat , join our slack community and ping us there we’re happy to discuss your own privacy circumstances and help you understand what’s required to meet the technical hipaa & gdpr requirements where to go from here? final project https //github com/virgilsecurity/chat back4app android/ if you missed pieces from the puzzle, open the e2ee project branch you can insert your application credentials in this code (as you did during the article) and build the project you can find more information about what you can build with virgil security here https //virgilsecurity com/?utm source=back4app\&utm medium=blog\&utm campaign=e2eechat