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 https //www back4app com/docs/get started/new parse app to learn how to create an app at back4app sign up for a virgil security account https //developer virgilsecurity com/account/signup?utm source=back4app\&utm medium=blog\&utm campaign=e2eechat (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 \<font color="#2166ae">main js\</font> and \<font color="#2166ae">package json\</font> in \<font color="#2166ae">scripts\</font> directory; open \<font color="#2166ae">main js\</font> with your favorite editor 1 1) get back4app credentials open \<font color="#2166ae">dashboard\</font> of your app > \<font color="#2166ae">app settings\</font> > \<font color="#2166ae">security \& keys\</font> in \<font color="#2166ae">main js\</font> , replace \<font color="#2166ae">parse app id\</font> with your \<font color="#2166ae">application id\</font> and \<font color="#2166ae">parse rest api key\</font> with your \<font color="#2166ae">rest api key\</font> 1 const parse app id = "your parse app id" ; 2 const parse rest api key = "your parse 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 \<font color="#2166ae"> env\</font> file under the e3kit section in the left side bar copy the values of \<font color="#2166ae">app id\</font> , \<font color="#2166ae">app key\</font> , and \<font color="#2166ae">app key id\</font> from the \<font color="#2166ae"> env\</font> file replace the copied values in your \<font color="#2166ae">main js\</font> file in the corresponding fields ( \<font color="#2166ae">main js\</font> of \<font color="#2166ae">scripts\</font> directory) 1 const app id = "your virgil app id" ; 2 const app key = "your virgil app key" ; 3 const app key id = "your virgil app id" ; 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 > \<font color="#2166ae">file\</font> > \<font color="#2166ae">new\</font> > \<font color="#2166ae">project from version control\</font> > \<font color="#2166ae">git\</font> git repository url https //github com/virgilsecurity/chat back4app android check out the \<font color="#2166ae">clean chat kt\</font> 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 \<font color="#2166ae">/app/src/main/res/values/strings xml\</font> file in your android project and replace \<font color="#2166ae">your back4app app id\</font> with your \<font color="#2166ae">application id\</font> and \<font color="#2166ae">your back4app client key\</font> with your \<font color="#2166ae">client key\</font> 1 \<string name="back4app app id">your back4app app id\</string> 2 \<string name="back4app client key">your back4app client key\</string> 2 3) setup db open back4app “dashboard” > “core” > “database browser” > “create a class” and create classes of \<font color="#2166ae">custom\</font> type named \<font color="#2166ae">message\</font> and \<font color="#2166ae">chatthread\</font> ; 2 4) setup live query go back to your back4app account https //dashboard back4app com/apps/#!/admin press the \<font color="#2166ae">server settings\</font> button on your application find the “web hosting and live query” block open the live query settings and check the \<font color="#2166ae">activate hosting\</font> option choose a name for your subdomain to activate live query for the 2 classes you created \<font color="#2166ae">message\</font> and \<font color="#2166ae">chatthread\</font> copy your new subdomain name and click the save button return to \<font color="#2166ae">/app/src/main/res/values/strings xml\</font> and paste “subdomain name” you have entered above into the \<font color="#2166ae">back4app live query url\</font> instead of “yoursubdomainname” \<string name="back4app live query url">wss\ //yoursubdomainname back4app io/\</string> 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 \<font color="#2166ae">message\</font> 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 ( \<font color="#2166ae">module app\</font> ) gradle at \<font color="#2166ae"> /app/build gradle\</font> add (but don’t sync gradle scripts just yet) implementation "com virgilsecurity\ ethree $rootproject ext ethree" add the following to the end of your project level \<font color="#2166ae">/build gradle\</font> ( \<font color="#2166ae">project chat back4app android\</font> ) 1 ext { 2 ethree = "2 0 5" 3 } now you can synchronize gradle scripts; update your \<font color="#2166ae">appvirgil\</font> class by adding new fields 1 companion object { 2 lateinit var ethree ethree 3 fun isethreeinitialized () = ethree isinitialized 4 } press \<font color="#2166ae">alt+ enter\</font> to import the necessary libraries into the class 2 authenticate users with back4app cloud code in \<font color="#2166ae"> /virgilsecurity/virgilback4app/model/\</font> directory, create data classes \<font color="#2166ae">authenticateresponse\</font> and \<font color="#2166ae">virgiljwtresponse\</font> that represent responses from cloud code functions 1 data class authenticateresponse ( val authtoken string ) 2 3 data class virgiljwtresponse ( val virgiltoken string ) in \<font color="#2166ae"> /virgilsecurity/virgilback4app/util/\</font> create \<font color="#2166ae">authrx\</font> 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 \<font color="#2166ae">preferences\</font> class in \<font color="#2166ae"> /virgilsecurity/virgilback4app/util/ \</font> define constant 1 private const val key virgil token = "key virgil token" add functions \<font color="#2166ae">setvirgiltoken\</font> , \<font color="#2166ae">virgiltoken\</font> and \<font color="#2166ae">clearvirgiltoken\</font> 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 \<font color="#2166ae"> /virgilsecurity/virgilback4app/util/\</font> create \<font color="#2166ae">rxethree\</font> class 1 class rxethree ( val context context ) { 2 3 private val preferences = preferences instance (context) 4 } now, add \<font color="#2166ae">initethree\</font> function that initializes e3kit instance in \<font color="#2166ae">rxethree\</font> 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 \<font color="#2166ae">registerethree\</font> function that registers a new user to \<font color="#2166ae">rxethree\</font> 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 \<font color="#2166ae">loginpresenter\</font> class (in \<font color="#2166ae"> /virgilsecurity/virgilback4app/auth/)\</font> add \<font color="#2166ae">rxethree\</font> field 1 private val rxethree = rxethree(context) update the \<font color="#2166ae">requestsignup\</font> 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 \<font color="#2166ae">requestsignin\</font> method of \<font color="#2166ae">loginpresenter\</font> class (in \<font color="#2166ae"> /virgilsecurity/virgilback4app/auth/)\</font> 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 \<font color="#2166ae">threadslistfragment\</font> class (in \<font color="#2166ae"> /virgilsecurity/virgilback4app/chat/contactslist/)\</font> 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 \<font color="#2166ae">postcreateinit\</font> 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 \<font color="#2166ae">threadslistfragmentpresenter\</font> class in \<font color="#2166ae">virgilsecurity virgilback4app chat contactslist/\</font> 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 \<font color="#2166ae">findcard\</font> function to \<font color="#2166ae">rxethree\</font> class (in \<font color="#2166ae"> /virgilsecurity/virgilback4app/util/\</font> ) 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 \<font color="#2166ae">postcreateinit\</font> of \<font color="#2166ae">chatthreadfragment\</font> class (in \<font color="#2166ae"> /virgilsecurity/virgilback4app/chat/thread/\</font> ) 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 \<font color="#2166ae">chatthreadpresenter\</font> 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 \<font color="#2166ae">requestsendmessage\</font> 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 \<font color="#2166ae"> chatthreadrvadapter\</font> class (in \<font color="#2166ae"> /virgilsecurity/virgilback4app/chat/thread/)\</font> add fields 1 private var ethree ethree = appvirgil ethree 2 lateinit var interlocutorcard card implement message decryption in \<font color="#2166ae">onbindviewholder\</font> 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 \<font color="#2166ae">virgil card\</font> ’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