Security & Privacy
Создание GDPR-соответствующего зашифрованного чат-приложения
52 мин
как создать чат приложение, соответствующее требованиям gdpr введение привет, сообщество back4app! это гостевой урок от команды virgil security, inc https //virgilsecurity com/?utm source=back4app\&utm medium=blog\&utm campaign=e2eechat мы — криптографическая технология за end to end зашифрованным сообщением twilio https //www twilio com/blog/2016/05/introducing end to end encryption for twilio ip messaging with virgil security html наши друзья из back4app попросили нас показать, как создать end to end зашифрованное чат приложение на базе back4app в этом посте мы проведем вас через шаги, чтобы сделать простое android messenger приложение back4app end to end зашифрованным! вы готовы? p s если вам не важны детали, просто пропустите в конец поста и скачайте финальный продукт что такое end to end шифрование? сначала давайте быстро освежим, что такое e2ee (end to end шифрование) и как оно работает e2ee просто когда вы вводите сообщение в чате, оно шифруется на вашем мобильном устройстве (или в вашем браузере) и расшифровывается только тогда, когда ваш собеседник получает его и хочет отобразить в окне чата сообщение остается зашифрованным, пока оно передается по wi fi и интернету, через облако / веб сервер, в базу данных и обратно к вашему собеседнику другими словами, ни одна из сетей или серверов не имеет понятия о том, о чем вы двое разговариваете сложность end to end шифрования заключается в задаче управления ключами шифрования таким образом, чтобы только пользователи, участвующие в чате, могли получить к ним доступ, и никто другой и когда я пишу «никто другой», я действительно это имею в виду даже сотрудники вашего облачного провайдера или даже вы, разработчик, исключены; никаких случайных ошибок https //techcrunch com/2017/11/29/meet the man who deactivated trumps twitter account/ или юридически принудительных подглядываний не допускаются написание криптографии, особенно для нескольких платформ, сложно генерация истинно случайных чисел, выбор правильных алгоритмов и выбор правильных режимов шифрования — это всего лишь несколько примеров, которые заставляют большинство разработчиков махать руками в воздухе и в конечном итоге просто не делать этого этот блог пост покажет вам, как игнорировать все эти раздражающие детали и быстро и просто осуществить сквозное шифрование с использованием sdk от virgil для начала, вот как мы обновим мессенджер back4app, чтобы он поддерживал сквозное шифрование во время регистрации мы сгенерируем индивидуальные закрытые и открытые ключи для новых пользователей (не забывайте открытый ключ получателя шифрует сообщения, а соответствующий закрытый ключ получателя расшифровывает их) перед отправкой сообщений вы зашифруете сообщения чата с помощью открытого ключа получателя после получения сообщений вы расшифруете сообщения чата с помощью закрытого ключа получателя мы опубликуем открытые ключи пользователей в сервисе карт virgil, чтобы пользователи чата могли находить друг друга и шифровать сообщения друг для друга закрытые ключи останутся на устройствах пользователей сделайте это просто это самая простая возможная реализация e2ee чата, и она идеально подходит для простых чат приложений между 2 пользователями, где разговоры недолговечны, и потеря истории сообщений допустима, если устройство потеряно вместе с приватным ключом хорошо, хватит болтать! давайте перейдем к коду мы начнем с того, что проведем вас через настройку android приложения, затем мы добавим немного кода, чтобы сделать приложение сквозным шифрованным предварительные требования чтобы завершить этот учебник, вам нужно android studio приложение, созданное на back4app следуйте за учебником по созданию нового приложения чтобы узнать, как создать приложение на back4app зарегистрируйтесь для получения учетной записи virgil security (мы создадим приложение позже) давайте настроим «чистое» приложение мессенджера back4app 1 настройте ваш сервер приложений давайте начнем с развертывания облачной функции для этого вам нужно найдите main js main js и package json package json в scripts scripts директории; откройте main js main js в вашем любимом редакторе 1 1) получите учетные данные back4app откройте панель управления панель управления вашего приложения > настройки приложения настройки приложения > безопасность и ключи безопасность и ключи в main js main js , замените parse app id parse app id на ваш id приложения id приложения и parse rest api key parse rest api key на ваш rest api ключ rest api ключ 1 2) получите учетные данные virgil создайте приложение на панели управления virgil https //dashboard virgilsecurity com/ откройте ваше новое приложение virgil, перейдите в раздел e3kit и сгенерируйте env env файл в разделе e3kit в левой боковой панели скопируйте значения app id app id , app key app key , и app key id app key id из файла env env замените скопированные значения в вашем main js main js файле в соответствующих полях ( main js main js из scripts scripts директории) 1 3) разверните функцию облачного кода откройте back4app “dashboard” вашего приложения > “core” > функции облачного кода; нажмите +добавить и выберите ваш main js и package json (из каталога скриптов), после этого переместите их в облачную папку; нажмите развернуть 2 запустите чистое демо приложение back4app на kotlin не забудьте сначала настроить функцию облачного кода back4app это обязательная часть этого демо после этого выполните следующие шаги 2 1) импортируйте проект в android studio откройте android studio > файл файл > новый новый > проект из системы контроля версий проект из системы контроля версий > git git url репозитория git https //github com/virgilsecurity/chat back4app android переключитесь на clean chat kt clean chat kt ветку важно! выберите тип файла «проект» он будет использоваться на протяжении всего учебника 2 2) настройте учетные данные back4app в проекте откройте «панель управления» back4app вашего приложения > «настройки приложения» > «безопасность и ключи»; перейдите в /app/src/main/res/values/strings xml /app/src/main/res/values/strings xml файл в вашем проекте android и замените your back4app app id your back4app app id на ваш application id application id и your back4app client key your back4app client key на ваш client key client key 2 3) настройка бд откройте back4app “dashboard” > “core” > “database browser” > “создать класс” и создайте классы типа custom custom с именем message message и chatthread chatthread ; 2 4) настройка живого запроса вернитесь к вашему аккаунту back4app https //dashboard back4app com/apps/#!/admin нажмите кнопку настройки сервера настройки сервера на вашем приложении найдите блок “веб хостинг и живой запрос” откройте настройки живого запроса и проверьте опцию активировать хостинг активировать хостинг выберите имя для вашего поддомена, чтобы активировать живой запрос для 2 классов, которые вы создали сообщение сообщение и чатпоток чатпоток скопируйте имя вашего нового поддомена и нажмите кнопку сохранить вернитесь к /app/src/main/res/values/strings xml /app/src/main/res/values/strings xml и вставьте “имя поддомена”, которое вы ввели выше, в back4app live query url back4app live query url вместо “вашеимяподдомена” после этих шагов вы сможете нажать кнопку запуск в android studio и запустить пример используйте эмулятор или реальное устройство для тестирования 3 запустите чистую демонстрацию чтобы увидеть результат запуска чистой версии демонстрации, вам нужно зарегистрировать 2 пользователей; начать разговор между ними и отправить несколько сообщений; 3\ откройте back4app “панель управления” > “ядро” > “обозреватель базы данных” > “сообщение” если все прошло успешно, вы должны увидеть приложение для обмена сообщениями зарегистрируйте двух пользователей и отправьте несколько сообщений друг другу вы должны увидеть новые данные, появляющиеся в сообщение сообщение классе обратите внимание, что вы можете видеть на сервере, о чем общаются ваши пользователи следующий закройте интерфейс чата и перейдите к следующему шагу – добавлению e2ee шифрования теперь давайте зашифруем эти сообщения с помощью сквозного шифрования! к концу этой части ваши сообщения в чате будут выглядеть так на сервере можете ли вы заметить разницу? как мы туда доберемся? очевидно, нам нужно реализовать сквозное шифрование, что означает, что нашему приложению необходимо сгенерировать пару закрытого и открытого ключей в процессе регистрации сохранить закрытый ключ в хранилище ключей на устройстве пользователя опубликовать открытый ключ в сервисе карт virgil как «карту virgil», чтобы другие пользователи могли скачать и зашифровать сообщения с его помощью зашифровать сообщения с помощью открытого ключа и подписать закрытым ключом; расшифровать сообщения с помощью закрытого ключа и проверить с помощью открытого ключа для этого нам нужно будет добавить e3kit в наше чистое демонстрационное приложение и немного кода, чтобы реализовать все, что было описано выше но прежде чем мы начнем, давайте проясним два важных термина для вас что такое карта virgil и закрытый ключ? карта virgil карты virgil содержат закрытые ключи пользователей карты virgil публикуются в сервисе карт virgil (представьте, что этот сервис похож на телефонный справочник), чтобы другие пользователи могли их получить алисе нужно получить открытый ключ боба, чтобы зашифровать сообщение для боба, используя этот ключ закрытый ключ закрытая часть ключа шифрования помните, закрытые ключи могут расшифровывать данные, зашифрованные с использованием соответствующего открытого ключа 1 добавьте e3kit в чистый демонстрационный проект e3kit back4app на kotlin в файле уровня приложения ( модуль app модуль app ) gradle в /app/build gradle /app/build gradle добавьте (но пока не синхронизируйте скрипты gradle) добавьте следующее в конец вашего проекта уровня /build gradle /build gradle ( проект chat back4app android проект chat back4app android ) теперь вы можете синхронизировать скрипты gradle; обновите ваш appvirgil appvirgil класс, добавив новые поля нажмите alt+ enter alt+ enter для импорта необходимых библиотек в класс 2 аутентификация пользователей с помощью back4app cloud code в /virgilsecurity/virgilback4app/model/ /virgilsecurity/virgilback4app/model/ директории создайте классы данных authenticateresponse authenticateresponse и virgiljwtresponse virgiljwtresponse которые представляют собой ответы от функций cloud code в /virgilsecurity/virgilback4app/util/ /virgilsecurity/virgilback4app/util/ создайте authrx authrx объект, который реализует вызовы к функциям cloud code (не забудьте импортировать все необходимые библиотеки после этого) 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 храните virgil jwt локально токен virgil, полученный от функций cloud code, необходимо хранить локально давайте обновим preferences preferences класс в /virgilsecurity/virgilback4app/util/ /virgilsecurity/virgilback4app/util/ определите константу добавьте функции setvirgiltoken setvirgiltoken , virgiltoken virgiltoken и 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 изменить регистрацию пользователя e3kit заботится о ваших закрытых и открытых ключах чтобы сгенерировать их в процессе регистрации, нам нужно сделать следующее в /virgilsecurity/virgilback4app/util/ /virgilsecurity/virgilback4app/util/ создайте rxethree rxethree класс теперь добавьте initethree initethree функцию, которая инициализирует экземпляр e3kit в rxethree rxethree классе 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 } добавьте registerethree registerethree функцию, которая регистрирует нового пользователя в rxethree rxethree классе e3kit генерирует пару ключей во время регистрации сгенерированный закрытый ключ затем сохраняется в локальном хранилище, а открытый ключ публикуется в службах virgil как карта virgil 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 } давайте внесем некоторые обновления в loginpresenter loginpresenter класс (в /virgilsecurity/virgilback4app/auth/) /virgilsecurity/virgilback4app/auth/) добавить rxethree rxethree поле 1 private val rxethree = rxethree(context) обновите requestsignup requestsignup функцию для выполнения регистрации с 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 изменить функции входа теперь давайте внесем изменения в requestsignin requestsignin метод класса loginpresenter loginpresenter (в /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 получить список существующих чатов далее добавьте функции, которые обрабатывают инициализацию e3kit в threadslistfragment threadslistfragment (в /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 } обновите postcreateinit postcreateinit функцию для инициализации 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 } и добавьте следующий код в threadslistfragmentpresenter threadslistfragmentpresenter класс в virgilsecurity virgilback4app chat contactslist/ virgilsecurity virgilback4app chat contactslist/ добавьте поле 1 private val rxethree = rxethree(context) и функция 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 } на данный момент мы можем зарегистрировать/войти в систему пользователя и создать новый чат с другим пользователем теперь давайте добавим шифрование для наших сообщений 7 шифрование и расшифровка сообщений давайте добавим findcard findcard функцию в rxethree rxethree класс (в /virgilsecurity/virgilback4app/util/ /virgilsecurity/virgilback4app/util/ ) которая поможет нам получить последнюю карту virgil по имени пользователя 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 } когда чат открыт, мы должны получить virgil card получателя измените postcreateinit postcreateinit класса chatthreadfragment chatthreadfragment (в /virgilsecurity/virgilback4app/chat/thread/ /virgilsecurity/virgilback4app/chat/thread/ ) заменив 1 presenter requestmessages(thread, 2 50, 3 page, 4 const tablenames created at criteria, 5 ongetmessagessuccess, 6 ongetmessageserror) код на новый 1 presenter requestcard(recipientid, 2 ongetcardsuccess, 3 ongetcarderror) и добавьте две функции 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 } теперь давайте изменим chatthreadpresenter chatthreadpresenter добавьте поля 1 private val ethree = appvirgil ethree 2 private lateinit var usercard card 3 private val rxethree = rxethree(context) добавьте функцию, которая получает карту вирджила получателя 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 } добавьте шифрование исходящих сообщений в requestsendmessage requestsendmessage функцию 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 } добавьте расшифровку всех входящих сообщений в chatthreadrvadapter chatthreadrvadapter класс (в /virgilsecurity/virgilback4app/chat/thread/) /virgilsecurity/virgilback4app/chat/thread/) добавить поля 1 private var ethree ethree = appvirgil ethree 2 lateinit var interlocutorcard card реализовать расшифровку сообщения в onbindviewholder onbindviewholder функции 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 запустите полный демонстрационный пример с сквозным шифрованием теперь, чтобы увидеть результат нашей полностью сквозной зашифрованной демонстрации, пройдите эти шаги снова выйдите из системы предыдущего пользователя зарегистрируйте 2 новых пользователя; начните разговор между ними и отправьте несколько сообщений; откройте back4app “панель управления” > “ядро” > “обозреватель базы данных” > “сообщение” важно! вы должны выйти из текущего пользователя и зарегистрировать двух новых пользователей, после этого вы сможете начать e2ee чат с этими двумя новыми пользователями причина в том, что у ваших первых двух пользователей нет virgil card virgil card ’ов, поэтому вы не можете использовать шифрование/расшифровку для них { blockquote tip} готово! теперь вы можете видеть, что сообщения пользователей зашифрованы и могут быть доступны в приложении только самим пользователям соответствие hipaa и gdpr сквозное шифрование — это способ соответствовать техническим требованиям hipaa (закон о переносимости и подотчетности медицинского страхования сша 1996 года) и gdpr (общее регламентирование защиты данных европейского союза) если вам нужны дополнительные детали, зарегистрируйтесь для бесплатного аккаунта virgil https //developer virgilsecurity com/account/signup?utm source=back4app\&utm medium=blog\&utm campaign=e2eechat , присоединяйтесь к нашему сообществу в slack и напишите нам там мы рады обсудить ваши собственные обстоятельства конфиденциальности и помочь вам понять, что требуется для соответствия техническим требованиям hipaa и gdpr куда идти дальше? финальный проект https //github com/virgilsecurity/chat back4app android/ если вы пропустили части головоломки, откройте ветку проекта e2ee вы можете вставить свои учетные данные приложения в этот код (как вы делали во время статьи) и собрать проект вы можете найти больше информации о том, что вы можете создать с помощью virgil security здесь https //virgilsecurity com/?utm source=back4app\&utm medium=blog\&utm campaign=e2eechat