Security & Privacy
开发符合GDPR的端到端加密聊天应用的技术指南
49 分
如何制作符合gdpr的聊天应用 介绍 你好,back4app社区! 这是来自 virgil security, inc https //virgilsecurity com/?utm source=back4app\&utm medium=blog\&utm campaign=e2eechat 我们是 twilio的端到端加密消息 https //www twilio com/blog/2016/05/introducing end to end encryption for twilio ip messaging with virgil security html 背后的加密技术。我们的朋友@back4app请我们向您展示如何在back4app上构建一个端到端加密的聊天应用。 在这篇文章中,我们将带您了解如何使简单的back4app android messenger应用实现端到端加密!你准备好了吗?ps:如果你不在乎细节,可以直接跳到文章末尾下载最终产品。 什么是端到端加密? 首先,让我们快速回顾一下什么是e2ee(端到端加密)以及它是如何工作的。e2ee很简单:当您在聊天中输入消息时,它会在您的移动设备(或浏览器)上加密,只有当您的聊天伙伴接收到它并想在聊天窗口中显示时才会解密。 消息在通过wi fi和互联网、云/网络服务器、进入数据库以及返回到您的聊天伙伴的过程中始终保持加密。换句话说,网络或服务器都不知道你们在聊天内容。 端到端加密中困难的地方在于以一种方式管理加密密钥,使得只有参与聊天的用户可以访问它们,而其他人无法访问。当我说“其他人无法访问”时,我是认真的:即使是您的云服务提供商的内部人员,甚至您自己,开发者,也无法访问; 没有意外错误 https //techcrunch com/2017/11/29/meet the man who deactivated trumps twitter account/ 或法律强制窥探都是不可能的。编写加密代码,尤其是针对多个平台的代码是困难的:生成真正的随机数、选择正确的算法和选择正确的加密模式只是一些让大多数开发者感到无能为力的例子,最终选择不去做。 这篇博客文章将向您展示如何忽略所有这些恼人的细节,并使用virgil的sdk快速简单地进行端到端加密。 作为介绍,我们将如何将back4app的消息应用程序升级为端到端加密: 在注册时:我们将为新用户生成单独的私钥和公钥(请记住:接收者的公钥加密消息,而匹配的接收者的私钥解密消息)。 在发送消息之前,您将使用接收者的公钥加密聊天消息。 在接收消息后,您将使用接收者的私钥解密聊天消息。 我们将把用户的公钥发布到virgil的卡片服务,以便聊天用户能够相互查找并能够为彼此加密消息。私钥将保留在用户设备上。 保持简单 这是e2ee聊天的最简单实现,它非常适合两个用户之间的简单聊天应用, 在这种情况下,谈话是短暂的,如果设备丢失了私钥,丢失消息历史也是可以接受的。 好吧,够了!让我们开始编码吧。 我们将首先指导您完成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 “仪表板” > “核心” > 云代码函数; 点击 +添加,选择你的 main js 和 package json(来自脚本目录),然后将它们移动到云文件夹; 点击部署。 2 启动干净的 back4app kotlin 演示应用 不要忘记先设置 back4app 云代码功能。这是本演示的强制部分。之后,按照以下步骤进行操作: 2 1) 在 android studio 中导入项目 打开 android studio > 文件 文件 > 新建 新建 > 从版本控制中导入项目 从版本控制中导入项目 > git git git 仓库 url 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 文件在你的安卓项目中,并替换 your back4app app id your back4app app id 为你的 应用程序 id 应用程序 id 和 your back4app client key your back4app client key 为你的 客户端密钥 客户端密钥 2 3) 设置数据库 打开 back4app “仪表板” > “核心” > “数据库浏览器” > “创建一个类”,并创建 自定义 自定义 类型的类,命名为 消息 消息 和 聊天线程 聊天线程 ; 2 4) 设置实时查询 返回到你的 back4app 账户 https //dashboard back4app com/apps/#!/admin 按下你的应用程序上的 服务器设置 服务器设置 按钮 找到“网络托管和实时查询”块 打开实时查询设置并检查 激活托管 激活托管 选项。 选择一个名称为你的子域名,以激活你创建的两个类的实时查询: 消息 消息 和 聊天线程 聊天线程 。 复制你的新子域名并点击保存按钮: 返回到 /app/src/main/res/values/strings xml /app/src/main/res/values/strings xml 并将你上面输入的“子域名”粘贴到 back4app live query url back4app live query url 而不是“yoursubdomainname”: 完成这些步骤后,您将能够在 android studio 中点击运行按钮并使示例正常工作。使用模拟器或真实设备进行测试。 3 运行干净的演示 要查看运行干净版本演示的结果,您需要: 注册 2 个用户; 在他们之间开始对话并发送几条消息; 3\ 打开 back4app “仪表板” > “核心” > “数据库浏览器” > “消息”。 如果一切顺利,您应该会看到聊天消息应用程序弹出。注册两个用户并互相发送几条消息:您应该会看到新的数据出现在 消息 消息 类中。 请注意,您可以在服务器上查看您的用户在聊天内容: 下一步 关闭您的聊天界面,继续下一步 – 添加端到端加密。 现在,让我们对这些消息进行端到端加密! 到这一部分结束时,您的聊天消息在服务器上的样子是这样的:您能发现区别吗? 我们如何到达那里?显然,我们需要实现端到端加密,这意味着我们的应用需要: 在注册时生成私钥和公钥对 将私钥存储在用户设备的密钥存储中 将公钥作为“virgil card”发布到virgil的卡片服务中,以便其他用户下载并使用它加密消息 使用公钥加密消息并用私钥签名;使用私钥解密消息并用公钥验证 为此,我们需要将e3kit添加到我们的干净演示应用程序中,并添加一些代码以实现上述所有内容。 但在我们开始之前,让我们为您澄清两个重要术语:什么是virgil card和私钥? virgil card 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 从cloud code函数接收到的virgil令牌需要本地存储。让我们更新 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 函数,该函数在 rxethree rxethree 类中初始化 e3kit 实例 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卡。编辑 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) 添加一个获取收件人 virgil 卡片的函数: 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 运行完整的端到端加密演示 现在要查看我们完全端到端加密演示的结果,请再次按照以下步骤进行操作: 注销当前用户 注册两个新用户; 在他们之间开始对话并发送几条消息; 打开 back4app “仪表板” > “核心” > “数据库浏览器” > “消息”。 重要! 您必须 注销 当前用户并注册两个新用户,之后您可以与这两个新用户开始 e2ee 聊天。原因是您的前两个用户没有 virgil 卡片 virgil 卡片 ,因此您无法对他们使用加密\解密。{ 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 项目分支。您可以在此代码中插入您的应用凭据(就像您在文章中所做的那样)并构建项目。 您可以在 这里 https //virgilsecurity com/?utm source=back4app\&utm medium=blog\&utm campaign=e2eechat 找到有关您可以使用 virgil security 构建的更多信息。