实现移动端到端加密的基础(一)

距我们首次发布web应用的端到端加密技术已有一段时间了。自那之后我们也一直在努力做出更新改进,其中值得一提的迭代项是我们用libolm和自动密钥协商引入了Double Ratchet算法

每位参与者都有一个随机生成的密钥来加密媒体。该密钥通过与Olm建立的E2EE通道(使用XMPP MUC私人信息)被分发给了其他参与者(以便他们可以解密媒体流)。更多内容详见白皮书

即使实际的加密/解密API在web和移动端上是不同的(比如“Insertable Stream”和本地加密器/解密器),密钥交换机制却可以在这两个(甚至三个,比如移动端同时有 Android和iOS)平台上保持一致。那么下一步要解决的是,我们如何能够在不做任何重大改变的情况下,重新使用Double Ratchet算法的JS网络实现,同时也兼顾对移动应用程序的性能影响呢?

因为我们的移动应用程序基于React Native运行,显而易见应该用wrap函数封装libolm。这样我们就可以使用和web相同的代码。但不是所有搭建出来的函数都是一样的。

经典解法——使用本地模块

出自Lorenzo S.的演讲

但是这个方法有三个主要缺点:

1. 性能不尽如人意。桥接的通信不仅可能会很慢,还可能需要一些非常特殊的数据作为参数,这些数据(可序列化的,可写的Arrays等)会被转换为JSON;

2. 不论是Android还是iOS,都需要大量粘合代码。另外,之后每一次变化都必须在Java和ObjC中实现;

3. 由于两个不同的线程运行本地代码和JS代码,桥接方法是异步的。这意味着不可能提供同步的API(如果桥接方法同步是可以提供的,但不建议这样做,因为会影响性能);

对我们上述这个特定用例来说,性能没有受到特别大影响,因为密钥交换发生的频率不高。但桥接方法的异步性绝对是个大问题,毕竟它会破坏web和移动端界面的一致性。

JavaScript接口–JSI

JavaScript接口(JSI)是JavaScript引擎和C++之间的一个新层。它提供了React Native中JS代码和本地C++代码间的通信手段。因为不需要序列化,所以它比传统的桥接方式快很多,而且还允许我们提供一个高性能的同步API。

出自Lorenzo S.的演讲

JSI还解决了本地模块带来的另外两个问题(下文会详述),实现的完成或修改必须一次完成(多数情况下如此,有时仍然需要一些粘合代码)。最重要的是,被调用的本地方法会认为JSI是可以同步的。

安卓系统

第一个问题是如何找到初始化C++库,和公开所谓的 “主机函数”(可以从JS代码中调用的C++函数)的适当方法。

为解决这个问题,我们利用本地模块机制,以及RN框架初始化本地模块的方式,创建了OlmModule.java和OlmPackage.java。OlmPackage就是一个简单的OlmModule作为本地模块的ReactPackage。

OlmModule.java

在ReactContextBaseJavaModule的生命周期中,它加载了C++库,把必要行为暴露给了JS端。

也就是说:C++库是在静态初始化器中加载的。

向JS暴露主机函数是在OlmModule的初始化方法中,通过JNI本地函数nativeInstall完成的。初始化方法在cpp-adapter.cpp中实现,且除了一些JNI特有的代码外,cpp还调用了jsiadapter::install,host函数也在这里被实际暴露了;Android专有粘合代码也在此结束;iOS也在使用跨平台的jsiadapter了(下面会详述)。

文章地址:https://jitsi.org/blog/a-stepping-stone-towards-end-to-end-encryption-on-mobile/
原文作者:Titus Moldovan



推荐阅读
相关专栏
开源技术
106 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和声网 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。