news 2026/9/11 20:38:07

Joplin 端到端加密同步快照深度解析:从 JED 密文格式到 Sync Version 3 迁移测试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Joplin 端到端加密同步快照深度解析:从 JED 密文格式到 Sync Version 3 迁移测试

Joplin 端到端加密同步快照深度解析:从 JED 密文格式到 Sync Version 3 迁移测试

【免费下载链接】joplinJoplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.项目地址: https://gitcode.com/GitHub_Trending/jo/joplin

Joplin 以"隐私优先的笔记同步"为核心能力,其端到端加密(E2EE)是保护笔记内容不落盘在第三方服务器上的关键机制。本文以仓库测试夹具 d7f300c742bb44338563ddbd6fb285c3.md 为主线,逐字节拆解 Joplin 加密同步条目的 JED 密文格式,并结合EncryptionServicesyncTargetUtils与迁移测试源码,说明同步目标快照(Sync Target Snapshot)体系如何生成、部署,以及 E2EE 快照在 Sync Version 3 迁移测试中承担的角色。读完本文,你将能独立解读任意一条 Joplin E2EE 同步文件,并复现快照的创建与迁移验证流程。

一、这份文档到底是什么:一条被 E2EE 加密的文件夹记录

该文件位于packages/app-cli/tests/support/syncTargetSnapshots/3/e2ee/目录下,是 Joplin 官方测试体系中的同步目标快照(sync target snapshot)夹具。它不是一个普通 Markdown 笔记,而是一条以 Joplin 同步文件格式序列化的、被端到端加密的数据条目。逐行解读其内容:

id: d7f300c742bb44338563ddbd6fb285c3 created_time: updated_time: 2021-08-07T17:03:37.028Z user_created_time: user_updated_time: encryption_cipher_text: JED01000022051f3b6b71948c4f5d909d1af6588c78bb0002c8{...} encryption_applied: 1 parent_id: 376c1a3fe5ce4fc885e344b52b9f37b8 is_shared: share_id: type_: 2

关键信息可归纳为下表:

字段含义
idd7f300c742bb44338563ddbd6fb285c3条目的全局唯一 ID(32 位十六进制),同时用作同步文件名
created_time/user_created_time观察可见,该快照中的条目创建时间未被填充,仅有updated_time
updated_time2021-08-07T17:03:37.028Z对应 Unix 时间戳 1628355817028,与同目录 info.json 中记录的updatedTime: 1628355817270处于同一同步时刻,即快照生成于 2021-08-07 前后
encryption_cipher_textJED0100...密文字段,承载 JED 头部与 SJCL 密文载荷(详见下一节)
encryption_applied1明文布尔标记,声明该条目已应用加密;该字段本身不加密
parent_id376c1a3fe5ce4fc885e344b52b9f37b8父条目 ID。查同目录 376c1a3fe5ce4fc885e344b52b9f37b8.md 可知其parent_id为空且type_同为 2,因此本文件是一条嵌套在顶层文件夹之下的子文件夹记录
type_2条目类型码。对照 BaseModel.ts 中的ModelType枚举:Note = 1Folder = 2Resource = 4Tag = 5NoteTag = 6MasterKey = 9,即本条目是一个文件夹

值得注意:idparent_idtype_is_sharedshare_idencryption_applied这些结构字段以明文保存在同步文件中,而真正的内容字段(如文件夹标题、笔记正文、标签列表等)被整体序列化后加密进encryption_cipher_text。这正是 Joplin 同步协议的取舍——同步器必须能通过明文的id/type_/parent_id建立条目树、执行增量比较,而内容本身对同步目标(如 Joplin Cloud、Nextcloud、WebDAV)保持不可读。

二、JED 加密格式逐层拆解

encryption_cipher_text的值以JED01000022051f3b6b71948c4f5d909d1af6588c78bb开头,这是 Joplin 自有的JED(Joplin Encrypted Data)容器头。对照 EncryptionService.ts 中encodeHeader_decodeHeaderBytes_的实现,可逐段解析:

片段字节数含义
标识符3JED密文头固定标识,decodeHeaderBytes_会首先校验该标识,缺失即报错
头部版本201头部模板版本 1,对应源码中headerTemplates_的模板定义
元数据长度6000022十六进制表示的元数据总长,0x22 = 34字节,其后 34 个字节即加密元数据
加密方法205padLeft(encryptionMethod.toString(16), 2, '0')生成,05对应 EncryptionMethod 枚举中的SJCL1a = 5
主密钥 ID321f3b6b71948c4f5d909d1af6588c78bb解密本条目所需主密钥的 ID,与 info.json 中的activeMasterKeyId一致

头部模板在源码中的定义(headerTemplates_,模板版本 1)为:

// 字段定义格式:[name, valueSize, valueType] 1: { fields: [['encryptionMethod', 2, 'int'], ['masterKeyId', 32, 'hex']], },

在头部之后、SJCL JSON 之前,还可以看到0002c8这样一个 6 位十六进制前缀(十进制 712)。从格式的帧结构可以推断:它用于声明紧随其后的密文载荷长度,使读取方解析完头部后即可确定密文边界(对应头部中的"元数据长度"字段同属这种长度前缀式帧设计)。

真正的密文主体是一个SJCL(Stanford JavaScript Crypto Library)格式的 JSON 对象

{ "iv": "/TciaFYKNHcgOGewTRhZ7Q==", "v": 1, "iter": 101, "ks": 128, "ts": 64, "mode": "ccm", "adata": "", "cipher": "aes", "salt": "tVgmTCWSasM=", "ct": "SDEcAeYxBnBVeHf+cPjW5pCSfGkSkHKZTQ50r3TnG1kdORfFV3Xgvchnjc10AdJxZs2QgMT7PoKioF+M9vWJbBbttiEZWVD2AdnnpwUtg3pYRenNvVmZFJ2TRc+llUrKoitgZ4iFUdeW6FD6zGRnXU+06apcmQFDZtHpqFEsFpIaoghYqV1SsjLEAt4sBKQPAFNpK3rJoQoVqVyqglMMAv3Z6UGSb2R8bkY6JxcJi86e1sJ9o/rovmco8cUhAWWa2TtZXvkFn36rTWM64caHW/BNGsM+xTiyvoLyNpWygsNSoRL1gzcLBn6WChdcK0UOaiQZHnWVeeHuMpcpR6lqyaFbf+g8yMtxRGhh4es9eG8F/rimV4aBcRnSdirPXJhLmw5H0OLaXtZOfy2RSYm7P7VqQ9BnYgRbqiZ3f/Bd0izciiekxIskvLWOK2hjc09YnaR0JjNQqsnuV4q9sgCnJkkKwXXv+k6WGlZFUTac4HUM3x+NIFa6yCHAt0D57uaiSJppmFG7ypR7xSTAKuNfGLHBaPlkyMFKDimIKf7RSjnpw1Ptkgbh3vxzg/royQ==" }

这些参数的含义:

  • cipher: "aes"mode: "ccm"——使用AES 分组密码的 CCM 认证加密模式(兼具机密性与完整性校验);
  • ks: 128——密钥长度 128 位;ts: 64——认证标签(tag)64 位;
  • iter: 101salt: "tVgmTCWSasM="——基于口令的主密钥经加盐、迭代派生密钥的参数;
  • iv——每次加密独立生成的初始化向量(nonce),保证同一密钥下不同条目密文互不相同;
  • ct——密文本体(上例已完整保留,未做截断省略)。

对比 info.json 中的主密钥内容(iter: 10000ks: 256encryption_method: 4),可以观察到 Joplin 的安全分层设计:主密钥自身使用更强的派生参数(10000 次迭代、256 位密钥)加密,而条目正文使用相对轻量的参数(101 次迭代、128 位密钥),在移动端性能与安全性之间取得平衡。

三、E2EE 背后的主密钥体系与加密服务

Joplin 的端到端加密采用"主密钥(Master Key)"体系:用户在客户端用主密码加密主密钥,主密钥再加密所有条目。这套逻辑全部收敛在 EncryptionService.ts 中:

  • 默认加密方法:源码第 74-76 行定义了defaultEncryptionMethod_ = EncryptionMethod.StringV1defaultFileEncryptionMethod_ = EncryptionMethod.FileV1defaultMasterKeyEncryptionMethod_ = EncryptionMethod.KeyV1。对应枚举StringV1 = 10FileV1 = 9KeyV1 = 8——现代 Joplin 对字符串、文件、主密钥分别采用专门的加密方法,而SJCL1a(方法 5)属于历史 SJCL 系列的兼容方法,本文拆解的夹具恰是这一时期的产物。
  • 分块大小chunkSize()(第 126-138 行)为不同方法定义了解密缓冲块:SJCLKeyV1为 5000 字节、FileV1为 131072 字节(128K)、StringV1为 65536 字节(64K)。源码注释还记录了实测性能规律——Node 环境下 1MB 数据解密很慢,移动端解密耗时随块增大呈指数级上升(50KB 约 1000ms,5KB 仅约 10ms),因此必须保持较小的分块。
  • 主密钥的同步元数据:同目录 info.json 以明文 JSON 记录了同步级状态:"version": 3(Sync Version 3)、"e2ee": {"value": true}(该目标启用了 E2EE)、"activeMasterKeyId"(当前生效主密钥 ID,与条目头中的1f3b6b71...一致)以及masterKeys数组。主密钥条目本身即ModelType.MasterKey = 9类型的同步项,source_application标记为net.cozic.joplintest-cli,说明该快照由测试版 CLI 应用生成。

四、同步目标快照体系:normal 与 e2ee 的双轨夹具

syncTargetSnapshots目录按版本号/类型/组织:当前仓库中存在版本1/2/3/,每种版本下又分normal/(未加密)与e2ee/(端到端加密)两套快照。这套结构的生成与消费逻辑集中在 syncTargetUtils.ts:

  • 测试数据模板testData,第 17-41 行):定义了一棵包含多层级文件夹、笔记、附件(resource: true)与标签(tags)的条目树,例如folder1 > subFolder2 > note1(含附件与 tag1)folder3 > note5等;
  • 数据创建与校验createTestData()递归按模板落库(文件夹Folder.save、笔记Note.save、附件shim.attachFileToNote、标签Tag.addNoteTagByTitle);checkTestData()反向校验每个条目、父级关系、附件 URL 与标签关联是否完整,是迁移测试中"数据未被改动"的判定器;
  • 快照生成入口main(syncTargetType)(第 119-146 行):限定syncTargetType只能是normale2ee;若为e2ee,先调用setEncryptionEnabled(true)loadEncryptionMasterKey()开启加密并加载主密钥,随后执行一次完整同步(synchronizerStart()+synchronizer().start()),最后把同步目录整体复制到${snapshotBaseDir}/${syncVersion}/${syncTargetType}形成快照;
  • 快照部署deploySyncTargetSnapshot(syncTargetType, syncVersion)(第 113-117 行):清空当前同步目录后把对应快照复制回去,模拟"一个旧版本客户端首次面对该同步目标"的场景。

normal/快照提供了绝佳的对照样本。例如 3/normal/933cf209b0094d43884c03149f034128.md 是一条明文附件条目,可见其完整元数据(mime: image/jpegsize: 2720type_: 4即 Resource,首行photo.jpg为附件文件名),且encryption_applied: 0。对比同一批测试数据在e2ee/版本中的形态——所有内容字段被加密成JED01...密文,仅保留结构字段——可以直观看到 E2EE 前后同步载荷的差异。

五、快照如何驱动 Sync Version 迁移测试

快照不是静态存档,而是同步协议版本迁移测试的"考古层"。在 synchronizer_MigrationHandler.test.ts 中:

  1. 文件头部注释明确了快照的再生成方式(与syncTargetUtils.main()配合):
// To create a sync target snapshot for the current syncVersion: // - In test-utils, set syncTargetName_ to "filesystem" // - Then run: // node tests/support/createSyncTargetSnapshot.js normal && node tests/support/createSyncTargetSnapshot.js e2ee
  1. testMigration(migrationVersion, maxSyncVersion)(第 65-97 行)的流程是:deploySyncTargetSnapshot('normal', migrationVersion - 1)部署旧版快照 →fetchSyncInfo断言当前版本 →migrationHandler().upgrade(migrationVersion)执行协议升级 → 再次fetchSyncInfo断言版本已提升 → 对最新版本执行synchronizer().start()后调用checkTestData(testData)验证数据无损,并切换到第二个客户端再同步一次以验证多端一致性。
  2. migrationTests表为每个版本定义了目录结构断言:版本 2 与 3 均要求同步目标根目录存在.resource/locks/temp/目录与info.json文件,且旧客户端版本标记.sync/version.txt内容为2——这说明 Sync Version 3 在目录布局上向后兼容 Version 2,仅通过info.jsonversion字段区分。
  3. 对应的 E2EE 路径testMigrationE2EE会先创建测试数据并开启加密,再部署 e2ee 快照执行同样的升级与完整性校验,本文拆解的d7f300c742bb44338563ddbd6fb285c3.md正是这一路径所需的数据底座之一。

六、延伸阅读:继续深入本仓库

  • 想了解加密服务的完整实现:阅读 EncryptionService.ts,重点看encodeHeader_/decodeHeaderBytes_(JED 头编解码)与chunkSize()(分块策略);
  • 想复现快照生成与迁移:阅读 syncTargetUtils.ts 与 synchronizer_MigrationHandler.test.ts;
  • 想了解同步元数据(info.json的读写逻辑、e2ee/activeMasterKeyId等键的时间戳优先级设计):阅读 syncInfoUtils.ts;
  • 想对照各同步版本快照全貌:浏览 syncTargetSnapshots 目录下1/2/3/normal/e2ee/两套夹具。

以一条 11 行的加密夹具文件为起点,本文还原了 Joplin E2EE 从"主密钥分层"到"JED 容器头 + SJCL/AES-CCM 密文"再到"快照驱动的协议迁移测试"的完整技术链路。理解这条链路后,无论是排查同步加密问题、阅读 Joplin 源码,还是为其他应用设计端到端加密同步格式,都具备了直接可用的知识基础。

【免费下载链接】joplinJoplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.项目地址: https://gitcode.com/GitHub_Trending/jo/joplin

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 20:34:17

ML-KWS-for-MCU源码级评测:Cortex-M上语音唤醒的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 20:33:50

2.4GHz同轴馈线选型实战指南:L50与L100深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 20:33:30

计算机毕业设计之jsp小区物业管理系统

随着信息化时代的到来,管理系统都趋向于智能化、系统化,小区物业管理系统也不例外,但目前不少小区仍都使用人工管理,小区规模越来越大,小区信息量也越来越庞大,人工管理显然已无法应对时代的变化&#xff0…

作者头像 李华