一个无感知的网盘加密实现方式探索

本文讲介绍一种无感知的网盘加密方式(以 OneDrive 为例),并在项目基本完工后开源代码

做个东西起因是最近弄到了一个 5TB 的 OneDrive 账号,感觉拿来做备份挺好的。试了下 OneDrive for Business 国内似乎会连香港的节点,上海联通上传大概是 10M 的带宽。但是说到个人数据就不得不谈隐私问题,我的数据会被别人获取到吗?

一番 Google 后发现,无论是 OneDrive 还是 GDrive,只要是域下的账号,管理员就有权限查看个人文件。虽然我的账号是自己的域下的,我仍旧对于把数据明文交给微软不那么放心。

目前有几种网盘加密的解决方案:Boxcryptor、Cryptomator、Duplicati 等。归纳一下大概有这么几种实现方式:

  1. 最蠢的实现:数据保留两份,一份加密版一份解密版,加密版直接丢 OneDrive 的同步文件夹下。
  2. 稍微好一些的实现:数据只保留加密版,同时启动另一个软件用于即时解密。该软件会让用户选择一个文件夹(Cryptomator),或创建一个虚拟驱动器(Boxcryptor),在使用时直接使用该目录即可拿到明文文件。

在我看来这两种实现都不那么聪明。因为我的需求只是对 OneDrive 或 GDrive 加密,本地应该存储明文,否则大量 IO 时会造成性能瓶颈。毕竟网盘的 IO 是绝对小于本地 IO 的,甚至远小于我的上传带宽。同时软件出错会直接影响我的数据安全,我也是很慌的。

我尝试做了一个无感知的网盘加密器,暂时起名叫 OneEncrypt 吧。它只包含一个可执行文件和一个动态链接库文件。

基本原理是通过映像劫持来启动我的可执行文件,该可执行文件其实只是一个启动器,负责启动 OneDrive(以调试模式避开 IFEO 并暂停执行),注入动态链接库文件。当动态链接库被加载后会尝试以 IAT Hook 的形式注入 IO 相关的系统调用。之后继续运行原程序,启动器退出。

在 OneDrive 正常运行过程中没有额外进程运行,同时 Hook 也只对 OneDrive 进程生效。这样在其 ReadFile 时可以额外做一下加密,在 WriteFile 时做解密。

对比其他的加密解决方案,这种方式有明显的好处:

  1. 性能好。这一点之前提到了,不再赘述。
  2. 兼容性好。本地程序直接读写非加密文件,而不是读写我的虚拟文件系统。例如在 Boxcryptor 中存放的 VHD 文件无法正常 Mount。
  3. 数据安全不依赖加密软件本身。加密软件出错或 key 丢失并不会影响本机数据。
  4. 用户零感知,无进程,并且无需改动 OneDrive 可执行文件本身,可以随意更新。
  5. 还可以做一些额外的 Tweak,比如 OneDrive 不支持同步软链接,那么可以在 Hook 中修改 API 的返回结果。

对于加密算法,需求是支持随机读取和高性能,比如 AES-CTRxchacha20。以 xchacha20 为例,加密过程中会获取该文件对应的 nonce ,对于文件的每个块,都会有一个顺序的 blockId,该 id 会用于块加密,块与块之间是独立的,所以当块大小设置成较小值时,可以认为是满足随机读写要求的。

参考 Boxcryptor 的实现,这里并没有将 nonce 放在文件开头,而是另外存在了 key.storage 的特殊文件中。这么做主要是为了避免麻烦。可能后续还需要改动。

目前的进度是基于 Detourslibsodium 库,完成了启动器和基本的 ReadFileWriteFile Hook,可以通过简单的 POC 测试;但是对于 ReadFileEx, SetFilePointer 等还没有充足的支持。

基本开发完成后会开源于 https://github.com/ihciah/OneEncrypt