ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

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

2026/9/11 20:38:10 拓冰建站 浏览量
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/joplinJoplin 以隐私优先的笔记同步为核心能力其端到端加密E2EE是保护笔记内容不落盘在第三方服务器上的关键机制。本文以仓库测试夹具 d7f300c742bb44338563ddbd6fb285c3.md 为主线逐字节拆解 Joplin 加密同步条目的 JED 密文格式并结合EncryptionService、syncTargetUtils与迁移测试源码说明同步目标快照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条目的全局唯一 ID32 位十六进制同时用作同步文件名created_time/user_created_time空观察可见该快照中的条目创建时间未被填充仅有updated_timeupdated_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 1、Folder 2、Resource 4、Tag 5、NoteTag 6、MasterKey 9即本条目是一个文件夹值得注意id、parent_id、type_、is_shared、share_id、encryption_applied这些结构字段以明文保存在同步文件中而真正的内容字段如文件夹标题、笔记正文、标签列表等被整体序列化后加密进encryption_cipher_text。这正是 Joplin 同步协议的取舍——同步器必须能通过明文的id/type_/parent_id建立条目树、执行增量比较而内容本身对同步目标如 Joplin Cloud、Nextcloud、WebDAV保持不可读。二、JED 加密格式逐层拆解encryption_cipher_text的值以JED01000022051f3b6b71948c4f5d909d1af6588c78bb开头这是 Joplin 自有的JEDJoplin Encrypted Data容器头。对照 EncryptionService.ts 中encodeHeader_与decodeHeaderBytes_的实现可逐段解析片段字节数值含义标识符3JED密文头固定标识decodeHeaderBytes_会首先校验该标识缺失即报错头部版本201头部模板版本 1对应源码中headerTemplates_的模板定义元数据长度6000022十六进制表示的元数据总长0x22 34字节其后 34 个字节即加密元数据加密方法205由padLeft(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。从格式的帧结构可以推断它用于声明紧随其后的密文载荷长度使读取方解析完头部后即可确定密文边界对应头部中的元数据长度字段同属这种长度前缀式帧设计。真正的密文主体是一个SJCLStanford JavaScript Crypto Library格式的 JSON 对象{ iv: /TciaFYKNHcgOGewTRhZ7Q, v: 1, iter: 101, ks: 128, ts: 64, mode: ccm, adata: , cipher: aes, salt: tVgmTCWSasM, ct: SDEcAeYxBnBVeHfcPjW5pCSfGkSkHKZTQ50r3TnG1kdORfFV3Xgvchnjc10AdJxZs2QgMT7PoKioFM9vWJbBbttiEZWVD2AdnnpwUtg3pYRenNvVmZFJ2TRcllUrKoitgZ4iFUdeW6FD6zGRnXU06apcmQFDZtHpqFEsFpIaoghYqV1SsjLEAt4sBKQPAFNpK3rJoQoVqVyqglMMAv3Z6UGSb2R8bkY6JxcJi86e1sJ9o/rovmco8cUhAWWa2TtZXvkFn36rTWM64caHW/BNGsMxTiyvoLyNpWygsNSoRL1gzcLBn6WChdcK0UOaiQZHnWVeeHuMpcpR6lqyaFbfg8yMtxRGhh4es9eG8F/rimV4aBcRnSdirPXJhLmw5H0OLaXtZOfy2RSYm7P7VqQ9BnYgRbqiZ3f/Bd0izciiekxIskvLWOK2hjc09YnaR0JjNQqsnuV4q9sgCnJkkKwXXvk6WGlZFUTac4HUM3xNIFa6yCHAt0D57uaiSJppmFG7ypR7xSTAKuNfGLHBaPlkyMFKDimIKf7RSjnpw1Ptkgbh3vxzg/royQ }这些参数的含义cipher: aes、mode: ccm——使用AES 分组密码的 CCM 认证加密模式兼具机密性与完整性校验ks: 128——密钥长度 128 位ts: 64——认证标签tag64 位iter: 101、salt: tVgmTCWSasM——基于口令的主密钥经加盐、迭代派生密钥的参数iv——每次加密独立生成的初始化向量nonce保证同一密钥下不同条目密文互不相同ct——密文本体上例已完整保留未做截断省略。对比 info.json 中的主密钥内容iter: 10000、ks: 256、encryption_method: 4可以观察到 Joplin 的安全分层设计主密钥自身使用更强的派生参数10000 次迭代、256 位密钥加密而条目正文使用相对轻量的参数101 次迭代、128 位密钥在移动端性能与安全性之间取得平衡。三、E2EE 背后的主密钥体系与加密服务Joplin 的端到端加密采用主密钥Master Key体系用户在客户端用主密码加密主密钥主密钥再加密所有条目。这套逻辑全部收敛在 EncryptionService.ts 中默认加密方法源码第 74-76 行定义了defaultEncryptionMethod_ EncryptionMethod.StringV1、defaultFileEncryptionMethod_ EncryptionMethod.FileV1、defaultMasterKeyEncryptionMethod_ EncryptionMethod.KeyV1。对应枚举StringV1 10、FileV1 9、KeyV1 8——现代 Joplin 对字符串、文件、主密钥分别采用专门的加密方法而SJCL1a方法 5属于历史 SJCL 系列的兼容方法本文拆解的夹具恰是这一时期的产物。分块大小chunkSize()第 126-138 行为不同方法定义了解密缓冲块SJCL与KeyV1为 5000 字节、FileV1为 131072 字节128K、StringV1为 65536 字节64K。源码注释还记录了实测性能规律——Node 环境下 1MB 数据解密很慢移动端解密耗时随块增大呈指数级上升50KB 约 1000ms5KB 仅约 10ms因此必须保持较小的分块。主密钥的同步元数据同目录 info.json 以明文 JSON 记录了同步级状态version: 3Sync 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.addNoteTagByTitlecheckTestData()反向校验每个条目、父级关系、附件 URL 与标签关联是否完整是迁移测试中数据未被改动的判定器快照生成入口main(syncTargetType)第 119-146 行限定syncTargetType只能是normal或e2ee若为e2ee先调用setEncryptionEnabled(true)与loadEncryptionMasterKey()开启加密并加载主密钥随后执行一次完整同步synchronizerStart()synchronizer().start()最后把同步目录整体复制到${snapshotBaseDir}/${syncVersion}/${syncTargetType}形成快照快照部署deploySyncTargetSnapshot(syncTargetType, syncVersion)第 113-117 行清空当前同步目录后把对应快照复制回去模拟一个旧版本客户端首次面对该同步目标的场景。normal/快照提供了绝佳的对照样本。例如 3/normal/933cf209b0094d43884c03149f034128.md 是一条明文附件条目可见其完整元数据mime: image/jpeg、size: 2720、type_: 4即 Resource首行photo.jpg为附件文件名且encryption_applied: 0。对比同一批测试数据在e2ee/版本中的形态——所有内容字段被加密成JED01...密文仅保留结构字段——可以直观看到 E2EE 前后同步载荷的差异。五、快照如何驱动 Sync Version 迁移测试快照不是静态存档而是同步协议版本迁移测试的考古层。在 synchronizer_MigrationHandler.test.ts 中文件头部注释明确了快照的再生成方式与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 e2eetestMigration(migrationVersion, maxSyncVersion)第 65-97 行的流程是deploySyncTargetSnapshot(normal, migrationVersion - 1)部署旧版快照 →fetchSyncInfo断言当前版本 →migrationHandler().upgrade(migrationVersion)执行协议升级 → 再次fetchSyncInfo断言版本已提升 → 对最新版本执行synchronizer().start()后调用checkTestData(testData)验证数据无损并切换到第二个客户端再同步一次以验证多端一致性。migrationTests表为每个版本定义了目录结构断言版本 2 与 3 均要求同步目标根目录存在.resource/、locks/、temp/目录与info.json文件且旧客户端版本标记.sync/version.txt内容为2——这说明 Sync Version 3 在目录布局上向后兼容 Version 2仅通过info.json的version字段区分。对应的 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),仅供参考