首页
/ SiYuan 加密笔记本全解析:从 KEK/DEK 密钥架构到孤岛式隔离的完整实现指南

SiYuan 加密笔记本全解析:从 KEK/DEK 密钥架构到孤岛式隔离的完整实现指南

2026-09-09 20:14:10作者:廉彬冶Miranda

导读

本文以 SiYuan 官方设计文档 docs/ENCRYPTED-NOTEBOOK.zh-CN.md(英文原版见 docs/ENCRYPTED-NOTEBOOK.md)为主体,结合仓库内核源码(kernel/confkernel/modelkernel/util 等)深入讲解加密笔记本这一特性的完整设计。你将理解:主密码如何通过 Argon2id 派生 KEK、再经 AES-256-GCM 包络解开每本笔记本独立的 DEK;加密笔记本如何以"孤岛"形态与普通笔记本物理隔离;以及启用、解锁、锁定、改密、同步、导入导出、备份恢复等全部实操流程与安全边界。读完本文,你可以独立评估加密笔记本的适用场景,并正确完成日常的加密笔记安全管理。

1. 设计目标与核心约束

加密笔记本是一种特殊笔记本:其 .sy 文档、assets 资源文件(含文件名)、数据库文件以及 SQLite 索引数据库(content + blocktree)全部在磁盘上加密存储,必须输入主密码解锁后才能查看内容。已有的普通笔记本完全不受影响,零迁移。

其核心约束如下:

维度 设计决策
加密范围 .sy + assets(含文件名)+ 数据库文件 + SQLite 数据库(content + blocktree)全部加密
笔记本身份 动态 boxID + BoxConf.Encrypted: true 标志位
数量 支持多个加密笔记本
密码 共用主密码(KEK 包络,每个笔记本独立 DEK)
解锁粒度 严格每笔记本单独解锁(KEK 用完即弃,每次运行 Argon2id 约 1 秒)
SQLite 数据库方案 每个加密笔记本物理独立的 SQLCipher SQLite 数据库,与全局 siyuan.db 隔离
全局功能 加密笔记本完全不参与(全局搜索/关系图/块引都看不到它)
块引 笔记本内部正常引用;禁止跨加密边界引用(双向:普通↔加密、加密 A↔加密 B)
AI / LLM 不做功能层隔离——解锁后与普通笔记本一样可读可搜;锁定后无可用 DEK 句柄且专用入口拒绝访问
跨边界移动 禁止——会破坏数据一致性且泄漏
导入 支持——导入的 .sy.zip 和 Markdown 在落盘前用 DEK 加密,与手动创建的文档一致
重启行为 启动时强制关闭所有加密笔记本,用户需重新解锁
已有笔记本 完全不动,零迁移

隔离原则是整套设计的灵魂:每个加密笔记本是一个独立孤岛——与普通笔记本隔离,加密笔记本之间也互相隔离。数据、块引、数据库镜像、移动都不跨加密笔记本边界;解锁一个加密笔记本不影响其他加密笔记本的锁定状态。

2. 加密笔记本 vs 普通笔记本:完整对比

维度 普通笔记本 加密笔记本
创建 随时创建,无限制 需先启用加密功能(设主密码),创建时需验证主密码
打开(Mount) 直接打开 需先输主密码解锁(每次约 1 秒 Argon2id),应用取得受管 DEK 句柄
关闭(Unmount) 关闭即不可见 关闭 = 锁定(停止新访问 + 等待/取消在途任务 + 从受管密钥缓存移除 DEK + 删除加密 SQLite 数据库 + 尽力清理明文缓存与临时文件)
重启后 保持上次打开状态 强制关闭,需重新输主密码解锁
.sy 文件 明文 JSON 落盘 AES-256-GCM 密文落盘,读时透明解密
assets 文件 明文二进制,原始文件名 AES-256-GCM 密文,文件名脱敏,原始名加密存储
数据库文件 全局 storage/av/<avID>.json(明文) 笔记本级 <boxID>/storage/av/<avID>.json(DEK 加密)
content SQLite 写入全局 siyuan.db(明文) 写入独立 siyuan-encrypted-<boxID>.db(SQLCipher)
blocktree SQLite 写入全局 blocktree.db(明文) 写入独立 siyuan-encrypted-<boxID>-blocktree.db(SQLCipher)
全局搜索 / 关系图 参与 不参与(数据不在全局 SQLite 数据库)
块引 可被任意笔记本引用 笔记本内部正常引用;禁止跨边界(双向)
跨笔记本移动 / Doc2Heading / 数据库镜像 支持 禁止跨加密边界
资源文件重命名 支持 不支持(脱敏文件名重命名会破坏映射)
导入 支持 支持(.sy.zip 和 Markdown,落盘前自动 DEK 加密)
文件历史 支持 支持(密文 .sy 原样存历史目录;历史条目保留 boxID、对象类型和稳定文件基名,查看/回滚以三者作为 AAD 上下文)
嵌入向量化 / 语义搜索 参与 不参与(加密数据不进全局 block_embeddings 表)
智能体 / AI 对话 / MCP 可读内容、可搜索 解锁后可用;锁定后不可达
内核 CLI 可操作工作区数据 无论锁定或解锁,均不支持加密笔记本及其文件
闪卡 / 书签 / 标签 参与 不支持(功能限制)
导出(.sy.zip) 支持 支持(解锁状态下导出明文,未解锁时拒绝)
数据同步 dejavu 同步明文 不改(密文进密文出,闭环自洽)
删除笔记本 删目录 删目录 + 删加密 SQLite 数据库文件 + 删笔记本级数据库存储
/api/file/getFile + putFile 可读写任意文件 拒绝读写加密笔记本下的任何文件(不只 .sy),合法读写走专用 API

核心区别总结:加密笔记本是"孤岛"——数据物理隔离、操作专用入口、完全不参与全局功能(全局搜索/关系图),文档和数据库文件不进出边界。笔记本内部功能(编辑、块引、反链、搜索、数据库、大纲、历史等)正常使用;解锁后 AI/LLM 也可用(与普通笔记本一致)。

3. 密钥架构:主密码 → KEK → DEK 的包络模型

用户主密码
    │ Argon2id(全局 MasterSalt, 64MB/3次/4线程)
    ▼
   KEK(密钥加密密钥,仅内存,用完即弃)
    │ AES-256-GCM 包络
    ├─→ 解 BoxConf.WrappedDEK → DEK₁ → 加密笔记本 1 的 .sy/assets/数据库文件/SQLite 数据库
    ├─→ 解 BoxConf.WrappedDEK → DEK₂ → 加密笔记本 2
    └─→ 解 BoxConf.WrappedDEK → DEK₃ → 加密笔记本 3
  • Argon2id:OWASP 2023 推荐参数,抗暴力破解(内存硬度)。
  • DEK:每笔记本独立 32 字节随机密钥,是真正加密数据的密钥。
  • KEK 包络:改密码只需重新包络 DEK,不重新加密数据。
  • 不缓存 KEK:每次解锁都派生,保持严格的笔记本间隔离。

3.1 源码级实现佐证

全局密钥管理结构 NotebookCrypto 定义在 kernel/conf/notebook_crypto.go,随 conf.json 持久化,字段包括 EnabledMasterSaltKDFParams(Argon2id 参数)、KEKVerifier(用 KEK 经 AES-GCM 加密的固定魔数,用于离线校验主密码)、VerifierNonceAutoLockMinutes(自动锁定闲置分钟数,0 禁用,默认 5),以及备份完整性字段 Spec/BackupID/CreatedAt/Checksum/KEKMAC。源码注释明确指出:MasterSalt 与 KEKVerifier 设计为可明文存储——salt 不保密,verifier 本身是密文。

单个笔记本的密钥包络参数 BoxEncryption 定义在 kernel/conf/box.go,挂在 BoxConf.BoxCrypt 上(仅 Encrypted=true 时有值),包含 Spec(包络规范版本,1 表示 WrappedDEK 已绑定 boxID AAD)、WrappedDEK(用 KEK 经 AES-GCM 加密后的 DEK,可落盘)、WrapNonceCreatedAt(毫秒时间戳,便于未来按时间轮换密钥)。

Argon2id 参数与 AES-GCM 信封实现在 kernel/util/kdf.go

  • DefaultArgon2Params() 返回 OWASP 2023 推荐值:Memory: 64*1024 KB、Iterations: 3Parallelism: 4KeyLength: 32
  • ValidateArgon2Params() 校验参数在合理范围内(内存 64–256 MB、迭代 3–10 次、并行度 1–16、输出 32 字节),防止恶意备份设置极大内存导致 OOM,或过弱参数降低安全性。
  • 加密信封格式为 magic(4B "SENC") || spec(1B) || algorithm(1B) || nonceLength(1B) || nonce || ciphertext || GCM tag(16B),每次加密生成随机 nonce。
  • DeriveSubKey()HKDF-SHA256 从主 DEK 派生用途隔离的子密钥,实现用途分离——.sy、assets、数据库定义各用独立子密钥,互不可替代,限制单点密钥泄漏的影响面。

用途子密钥在 kernel/model/crypto.go 中落地:EncryptFile/DecryptFile(约 L1516–L1533)用 DeriveSubKey(dek, "siyuan/file") 并绑定 boxID + 稳定文件基名 的 AAD;EncryptAsset/DecryptAsset(约 L1537–L1548)用 "siyuan/asset" 子密钥,AAD 为 siyuan:v1:asset:<boxID>:assets/<磁盘文件名>;资源名映射 EncryptAssetNameMapping 同样复用 asset 子密钥但使用独立 AAD siyuan:v1:asset-names:<boxID>

4. MasterSalt 备份与跨设备恢复

MasterSalt 是 KEK 派生的全局根基——主密码 + MasterSalt 经 Argon2id 派生 KEK,KEK 再解开各笔记本的 WrappedDEK。MasterSalt 丢失等于数据永久锁死:即使输入相同主密码,因 salt 变化派生出的 KEK 不同,旧 WrappedDEK 无法解开。为此引入备份与恢复机制。

备份<DataDir>/.siyuan/notebook-crypto-backup.json,保存整套 NotebookCrypto(MasterSalt/KEKVerifier/KDFParams)。位于 DataDir 内,进入 dejavu 同步范围。启用加密笔记本、修改主密码时刷新;只有禁用前确认不存在现存加密笔记本,也不存在依赖该备份的内核可枚举历史或恢复快照,或用户明确选择永久清除这些恢复数据后,才能删除。备份文件按明文 JSON 存储(salt 不保密、verifier 是密文,与 conf.json 的存储方式一致)。

恢复触发点(覆盖 conf.json 丢失 / 同步到新设备 / 导入 Data.zip 等场景):

触发场景 函数 需要主密码? 说明
同步完成后(拉取到备份文件) restoreNotebookCryptoConfigFromBackup 把配置装回本机 conf.json 并置 Enabled=true,UI 显示"已启用、锁定"
导入 Data.zip 后 restoreNotebookCryptoConfigFromBackup 同上,覆盖"备份随 Data.zip 到达本机但本机未启用"的情况
用户输主密码解锁(兜底) tryRestoreNotebookCryptoFromBackup deriveKEK 在本机 Enabled=false 时尝试恢复,校验主密码后返回 KEK
用户手动启用(防呆) tryRestoreNotebookCryptoFromBackup 已存在加密笔记本时拒绝生成新 salt,改为从备份恢复并校验主密码

配置恢复 vs 密钥恢复分离:配置恢复只需读备份文件、装回 salt/verifier(不需主密码,因 salt 不保密、没主密码仍解不开数据);密钥恢复才需主密码派生 KEK 并校验 verifier。这让同步/导入后能自动进入"已启用"状态,用户输主密码即可解锁。

手动导出/导入密钥:除自动同步外,可在 设置 → 访问授权 → 加密笔记本 手动导出/导入密钥备份,作为同步之外的独立恢复途径(如同步不可用、跨账号迁移、物理离线转存)。

操作 入口 说明
导出密钥 已启用状态下显示 notebook-crypto-backup.json 复制到 export 目录供下载(API:/api/notebook/exportNotebookCryptoBackup
导入密钥 未启用状态下显示 选本地备份文件上传,校验合法后写回 <DataDir>/.siyuan/ 并装回本机配置(Enabled=true)。后续用该密钥对应的主密码解锁(API:/api/notebook/importNotebookCryptoBackup

导入防呆:本机已启用时拒绝导入(避免覆盖现有 salt 孤立 WrappedDEK),导入入口仅在未启用时露出。备份文件本身不含主密码,导出/导入不涉及明文数据泄漏,解锁仍需主密码。

源码中的防呆守卫实现于 kernel/model/crypto.gohasEncryptedNotebook() 检查数据目录中是否存在 Encrypted=true 的笔记本,HasEncryptedNotebookHistory() 检查历史目录中是否存在加密笔记本的历史快照;EnableEncryptedNotebook 在存在这些情况时禁止重新生成 MasterSalt(否则会孤立旧 WrappedDEK),改为优先从备份恢复;DisableEncryptedNotebook 对称地扫描现存笔记本、已删除笔记本历史和本地恢复快照,存在依赖时拒绝禁用。备份文件由 saveNotebookCryptoBackup 写入,且无 KEK 时不得生成当前格式备份(KEKMAC 缺失会被恢复路径拒绝)。此外还有 masterPasswordMigrationPath/recoverMasterPasswordMigration 实现的改密迁移清单机制,确保改密过程中不会出现"部分笔记本仅能用新密码解锁"的不一致状态。

5. 加密文件布局

<workspace>/
├── conf/conf.json                          ← 全局加密配置(MasterSalt/KEKVerifier)
├── storage/av/                             ← 普通笔记本的数据库文件(明文)
├── data/
│   ├── .siyuan/
│   │   └── notebook-crypto-backup.json     ← NotebookCrypto 备份(MasterSalt/KEKVerifier,进同步范围)
│   ├── <boxID>/                            ← 加密笔记本目录
│   │   ├── .siyuan/conf.json               ← BoxConf(Encrypted=true + WrappedDEK)
│   │   ├── .siyuan/notebook-crypt-backup.json ← 笔记本级 BoxCrypt 备份(WrappedDEK/WrapNonce)
│   │   ├── *.sy                            ← AES-256-GCM 密文
│   │   ├── assets/
│   │   │   ├── <uuid>-<blockID>.ext        ← AES-256-GCM 密文,文件名已脱敏
│   │   │   └── .names.json                 ← 原始文件名映射(DEK 加密)
│   │   └── storage/av/
│   │       └── <avID>.json                 ← 数据库文件(DEK 加密)
│   └── <normalNotebookID>/                  ← 普通笔记本(明文,不动)
├── history/                                ← 历史目录(密文原样存储)
│   └── <timestamp>-update/
│       └── <boxID>/                        ← 加密笔记本的历史(密文 .sy + 密文数据库文件)
└── temp/
    ├── siyuan.db                           ← 全局 SQLite(明文,不含加密笔记本数据)
    ├── blocktree.db                        ← 全局 blocktree(明文,不含加密笔记本数据)
    ├── siyuan-encrypted-<boxID>.db         ← 加密笔记本 content SQLite 数据库(SQLCipher)
    └── siyuan-encrypted-<boxID>-blocktree.db ← 加密笔记本 blocktree SQLite 数据库(SQLCipher)

注意两级备份的分工:全局 notebook-crypto-backup.json 存 MasterSalt/KEKVerifier;每个笔记本的 <boxID>/.siyuan/notebook-crypt-backup.json 存 WrappedDEK/WrapNonce,在主 conf.json 丢失时作为"此笔记本是加密笔记本"的标识和降级恢复源。后者由 writeNotebookCryptBackup/readNotebookCryptBackup 管理(kernel/model/crypto.go L1566–L1603)。

6. SQLite 数据库隔离设计

普通笔记本操作:
  前端 → API → 原函数 → 全局 blocktree.db → 全局 siyuan.db

加密笔记本操作(专用读路径,带 boxID):
  前端(带 notebook) → API handler 分流 → InBox 函数
    → 加密 blocktree SQLite 数据库 → 加密 content SQLite 数据库
  • 写路径:集中收口,按笔记本选 SQLite 数据库。索引队列、blocktree 写函数、索引订正全部按笔记本路由。
  • 读路径:三层 fall-through——handler 层按 notebook 参数分流;原函数转发到 InBox 版(普通路径传空 boxID);底层 wrapper 按加密 SQLite 数据库是否打开决定路由,空 boxID 必走全局 SQLite 数据库。
  • 深度全路由原则:加密笔记本读操作经过的每一个 SQL 调用(含深层 helper:引用计数、块名/别名、子块递归、FTS 计数、高亮、mention 搜索、refs 定义查询等)都路由到加密 SQLite 数据库,绝不泄漏到全局 SQLite 数据库。
  • 通用入口 fallbackGetBlockTree / ExistBlockTree / GetBlockTrees / LoadTreeByBlockID 等核心函数全局查不到时自动遍历已打开的加密笔记本。

7. .sy / assets / 数据库文件加解密

.sy 透明加解密(filesys 层):

  • DEKProvider 回调注入(避免循环依赖)。
  • 读盘后解密,写盘前加密,缓存存明文。
  • 加密笔记本的标题读取需整体读 + 解密再解析。
  • .sy AAD 绑定 boxID、对象类型和稳定文件基名 <rootID>.sy,不包含父目录;文件基名必须与解密后根块 ID 一致,并在笔记本内全局唯一。
  • 父目录不属于 AAD,应用仍按现有目录结构构建文档层级;加载时校验文件基名与解密后根块 ID 一致,并拒绝同一笔记本内的重复对象 ID、非法路径和循环层级,但不提供父目录关系的密码学完整性。

assets 加密

  • 上传时全量读入内存 + 加密 + 写盘(无法流式加密)。
  • 文件名脱敏:加密笔记本的 asset 文件名为 <uuid>-<blockID>.<ext>,原始名存加密映射文件。
  • 读取时解密后输出,下载时查映射用原始名。
  • 加密笔记本禁用全局 assets 回退,强制笔记本级。
  • 资源文件重命名禁止(脱敏文件名重命名会破坏映射)。
  • 资源密文与加密名称映射作为一个带日志的可恢复事务提交:先写同目录临时文件并同步落盘,再写不含明文名称的事务日志,随后替换两个目标并同步目录,最后清除日志;崩溃恢复必须在开放访问前完成前滚或回滚,任何失败都向调用方返回错误,不得留下对外可见的孤儿资源或缺失映射。

数据库文件加密

  • 路径 fallback:加密笔记本的数据库文件存 <boxID>/storage/av/<avID>.json,普通笔记本仍存全局 storage/av/
  • 读取:先查全局路径,找不到时遍历已打开的加密笔记本;找到后 DEK 解密再 JSON 解析。
  • 保存:JSON 序列化后按路径路由(全局明文 / 加密笔记本 DEK 加密)。
  • 首次创建:RenderAttributeView 从 blockID 反查 boxID 并预设归属。
  • 镜像:禁止加密笔记本参与跨边界镜像;加密笔记本内部镜像存笔记本级 blocks.msgpack

8. 历史功能的安全设计

文件历史:
  编辑触发历史生成 → 密文 .sy 原样拷到历史目录
  关闭/退出前自动生成历史 → 确保锁定后定时器无法生成时不丢历史
  历史索引 → content 留空(密文不进搜索索引)
  查看历史 → 读取快照 boxID + 对象类型 + 稳定文件基名,以三者构造原 AAD 解密(需笔记本已解锁)
  回滚 → 按快照 AAD 解密历史 → 校验根块 ID 与文件基名一致 → 加载 tree → 文件基名不变时可原样恢复密文,变化时由 WriteTree 重新封装

已删除笔记本历史:
  删除前 → 整个目录密文备份到历史目录
  恢复 → 使用匹配的全局密钥备份和主密码验证 WrappedDEK 后,将密文目录原样拷回
  密钥保留 → 只要内核管理的该类历史仍可恢复,就禁止删除其依赖的全局密钥备份

数据库历史:
  生成 → 加密笔记本的数据库文件从笔记本级目录拷到历史目录
  查看 → 按快照记录的 boxID 与对象用途/标识解密,不使用历史目录绝对路径作为 AAD
  回滚 → 加密笔记本的数据库文件/资源回滚到笔记本级目录

历史文件在历史目录中的父路径不是 AAD 的组成部分。每个历史条目必须以不可歧义的规范格式保留快照写入时的 boxID、对象类型和稳定文件基名或对象标识。文档只改变父目录时 AAD 上下文保持不变;文件基名改变时,既有历史仍使用旧基名认证解密。回滚时先验证密文、文件基名与解密后对象 ID 一致,再按目标父目录恢复;仅当目标文件基名变化时才按新基名重新封装。快照上下文缺失、格式不合法或与密文不匹配时必须拒绝查看和回滚,不得把密文当明文解析或使用当前文件基名替代快照基名。

9. 块引跨加密边界防护

加密笔记本的块引:笔记本内部正常使用,跨加密边界被禁止。三层防御:

  1. 前端搜索分流:加密笔记本内输入 (({{ 触发搜索时,请求带 notebook 参数,内核只搜该笔记本自己的加密 SQLite 数据库,结果不含其他笔记本的块。
  2. handler 分流:加密笔记本调专用搜索版。
  3. 落库兜底校验:事务落库前遍历树,对每个块引节点判定是否跨边界,跨边界则降级为纯文本(清空引用属性,保留锚文本)。防御手工输入块 ID、拖拽、粘贴、API 直调。

10. 跨加密边界移动:为何禁止

加密笔记本禁止文档跨加密边界移动(普通 ↔ 加密,双向)。

数据损坏风险:跨笔记本移动用文件系统 Rename 直搬 .sy 字节流,不经加解密转换。密文搬到普通目录不可读,明文搬到加密目录不可读。索引也无法跨库迁移。

同一加密笔记本内移动.sy 密文 AAD 只绑定稳定文件基名,不绑定父目录。文件基名不变的移动按普通笔记本的同箱移动语义原样 Rename 密文,不需要重新封装根文档或子文档;只有文件基名本身变化时才使用旧基名验证解密并按新基名重新封装。移动完成后更新索引,失败处理与普通笔记本一致,不额外引入加密层级元数据。

安全泄漏风险(更关键):从加密笔记本移动到普通笔记本时——文档正文脱离加密保护;关联 resources 必须一起搬出解密;绑定块与子文档的引用网被割裂或连带泄漏;索引元数据跨库迁移破坏隔离。

设计立场:加密笔记本是孤岛,内容不进出(从加密笔记本移出到普通笔记本会泄漏明文;反之会损坏密文)。

11. 交互设计总览

场景 交互
启用 设置 → 访问授权 → 加密笔记本区块 → 开关 → 设主密码(双输入 + 风险确认)
禁用 仅在无现存加密笔记本且无依赖当前密钥备份的内核可枚举历史或恢复快照时支持关闭;否则拒绝,或要求明确永久清除这些恢复数据
创建 文件面板"更多"菜单 → "新建加密笔记本" → 输名字 + 主密码 → 自动解锁并打开
图标 关闭(锁定)时显示锁图标;打开(解锁)后恢复用户 emoji
解锁 点已关闭的加密笔记本 → 弹主密码框(🔓 解锁 xxx)→ 等约 1 秒 → 打开
锁定 等于关闭:先阻止新操作,等待/取消在途任务,再移除受管 DEK 句柄、删除加密 SQLite 数据库,并尽力清理明文缓存、临时文件和访问令牌。关闭前自动保存未写入的编辑和文件历史
自动锁定 每个已解锁笔记本独立保存最近活动时间;认证用户在当前工作区产生真实界面交互时,统一刷新所有已解锁笔记本的活动时间。无界面客户端可通过需认证与管理员权限的显式保活接口刷新;后台读取、同步、索引等非用户活动不自动保活
改密 设置 → 访问授权 → "修改主密码"
文档转标题 / 跨边界操作 跨加密边界被拒绝并提示
导出 支持(与普通笔记本一致;需先解锁,导出明文。锁定状态下拒绝)
同步 不改(密文进密文出,闭环自洽)

创建加密笔记本的流程在源码 kernel/model/crypto.goCreateEncryptedBox(约 L1638)中实现:先校验加密功能已启用,再用主密码临时派生 KEK(用完即弃,zeroAndClear 清零内存),调用 WrapNewDEK 生成并包络新 DEK,写入 BoxConf.Encrypted=trueBoxCrypt,并同步写笔记本级备份;任一步失败则回滚删除已创建的 box 目录和加密数据库文件,避免半创建状态。自动锁定由 AutoLockIdleEncryptedBoxesJob 周期任务实现,默认闲置 5 分钟(由 NotebookCrypto.AutoLockMinutes 控制,可通过 SetAutoLockMinutes 调整)。

12. 安全边界与威胁模型

安全前提:加密笔记本在关闭(锁定)状态下提供最强的应用级保护。锁定完成后,新操作被拒绝,受管 DEK 句柄和数据库连接已移除,内核管理的明文缓存、临时文件与令牌已尽力清理;这不承诺抹除 Go 运行时、操作系统交换区、崩溃转储或存储介质中的所有瞬时副本。打开(解锁)状态下,应用持有可用 DEK 句柄,此时已认证的应用内调用方——API、第三方插件、AI/LLM(含 MCP、智能体、语义搜索)——都可像读取普通笔记本一样读到明文内容。内核 CLI 是明确例外:无论锁定或解锁,都会拒绝加密笔记本及其原始文件。

保护(磁盘上密文)

  • .sy 文档正文、assets 二进制文件、assets 原始文件名(脱敏存储)
  • 数据库文件(列/行/cell 值/视图配置/内容快照)
  • content SQLite 数据库(blocks/FTS/attributes/refs,SQLCipher 加密)
  • blocktree SQLite 数据库(块树元数据:ID/路径/标题,SQLCipher 加密)
  • 历史目录中的 .sy 和数据库文件(密文原样存储)

不保护

  • conf.json 里的 MasterSalt/KEKVerifier(设计可明文:salt 不保密,verifier 是密文)
  • BoxConf.WrappedDEK(密文,需 KEK 解开)
  • 进程和操作系统可能产生的 DEK 派生值、明文瞬时副本、交换区、休眠映像或崩溃转储
  • 历史索引的 content 字段(留空,不存明文也不存密文)
  • 元数据泄漏(加密不掩盖以下信息):文件数量、目录结构、文件大小、修改时间(mtime)、asset 文件扩展名、blockID 时间戳;同步端/历史快照中的旧密文由各存储方自行保管,加密笔记本无法吊销其副本

API 防护

  • /api/file/getFile/api/file/putFile/api/file/copyFile/api/file/renameFile/api/file/removeFile:拒绝读写加密笔记本下的任何文件(不只 .sy),避免密文泄漏或明文破坏;合法读写走专用 API(已加密感知)。
  • 内核 CLI:拒绝以加密笔记本或块 ID 为目标的命令,以及 <工作区>/data/<加密笔记本 ID>/ 下的直接路径;不能用于解锁、读写、导出或操作加密笔记本数据。

威胁模型:本特性保护静态数据机密性和笔记本锁定后经受支持入口的不可访问性。设计假设攻击者可以取得工作空间、同步端或备份介质中的当前或历史密文,但不知道主密码,也不能在用户解锁期间控制运行中的应用或操作系统。AES-GCM 只提供单个加密对象的机密性、完整性和来源上下文认证,不提供可用性、版本新鲜度、回滚防护或已保留密文副本的删除。父目录不进入 AAD,因此能够写工作空间密文的攻击者可以在同一笔记本内移动一个文件基名不变的有效密文,应用会把新父目录视为当前文档层级。

13. AI / LLM 可达性:锁定即不可达

加密笔记本对 AI/LLM 的可见性完全由解锁状态决定,不做功能层隔离。

  • 锁定状态下:AI/LLM(含 MCP、智能体、语义搜索、嵌入向量化)无法取得可用 DEK 句柄,所有加密笔记本专用入口都拒绝访问;独立数据库已关闭,磁盘内容为密文,因此这些调用方读不到加密内容。
  • 解锁状态下:DEK 在内存,AI/LLM 可读加密笔记本内容、搜索笔记本内全文——MCP 工具能列出加密笔记本及其文档、读取块内容、运行笔记本内 FTS 搜索等。但全局搜索、语义搜索、嵌入向量化仍不参与(加密数据不进全局 block_embeddings/blocks 表,物理不可达)。

设计立场:不在功能层做"对 AI 隐藏"的隔离,因为这种隔离既不彻底,又会让安全模型复杂难懂。用户只需理解一条规则:敏感内容用完即锁

14. 功能限制:孤岛的边界

加密笔记本是孤岛,部分功能因其跨笔记本特性或依赖全局聚合而未实现,属于功能边界,非性能或安全取舍。

  • 闪卡 / 间隔重复:牌组与调度跨笔记本且依赖全局 SQLite 数据库,未实现。
  • 书签 / 标签:全局聚合视图(分别扫描全局 siyuan.db 与 spans 表),加密笔记本未接入。
  • 资源文件重命名:加密笔记本的 asset 文件名已脱敏为 uuid-blockID.ext,重命名会破坏原始名映射。
  • 未引用资源清理 / 未引用数据库清理:加密笔记本的资源与数据库定义不参与全局清理,避免锁定时因无法扫描引用关系而误删。

这些不支持的场景必须在前端、HTTP API、插件 API、MCP 和导入流程中一致处理:对加密笔记本目标返回明确的"不支持"错误,不创建全局索引、全局属性或延迟任务。

15. 性能区别

操作 普通笔记本 加密笔记本 差异原因
解锁(Mount) 即时 约 1 秒延迟 Argon2id 密钥派生(故意慢以抗暴力破解)
打开文档 读盘 + 解析 JSON 读盘 + AES-GCM 解密 + 解析 + 加密数据库查询 多一次解密 + 加密数据库查询
保存文档 渲染 JSON + 写盘 渲染 JSON + AES-GCM 加密 + 写盘 多一次加密
上传 assets 流式写盘 全量读入内存 + 加密 + 写盘 无法流式加密;大文件有内存压力
浏览 assets 直接 ServeFile 读盘 + 解密 + 输出 每次请求都解密,无浏览器缓存
数据库渲染 全局 blocktree + 明文 JSON 加密 blocktree + DEK 解密 JSON 多一次 JSON 解密;批量 IAL 加载按笔记本路由
笔记本内搜索 FTS 查全局 SQLite FTS 查独立加密 SQLite SQLCipher page 级解密约 5–10% 开销
数据库连接 1 个全局 1 个全局 + 每个解锁的加密笔记本 2 个 每加密数据库连接池约 20 连接
锁定时 无额外开销 受管密钥句柄撤销,并尽力清理缓存与临时文件 下次操作冷启动延迟

性能影响总结:日常编辑几乎无感知(AES-GCM 加解密微秒级,文档读写瓶颈在磁盘 IO);感知明显的延迟是解锁约 1 秒、大 assets 浏览(每次解密无缓存)、锁定后首次访问(冷启动);对普通笔记本零影响(加解密与路由逻辑对非加密笔记本短路跳过)。

16. 状态机与并发规则

全局状态为 DisabledEnabledRecoveryRequired。正常稳态下,Disabled 不得存在现存加密笔记本或内核可枚举的密钥依赖;如果启动时在全局配置缺失或禁用的情况下发现加密笔记本或恢复数据,必须进入 RecoveryRequired,拒绝把它们当作普通笔记本,直到恢复匹配的密钥备份。Enabled 下每个加密笔记本独立处于 LockedUnlockingUnlockedLockingError,应用启动后均从 Locked 开始。

转换 前置条件 成功后的不变量 失败处理
Locked → Unlocking → Unlocked 主密码验证成功且密文配置匹配 可用 DEK 句柄仅存在于受管内存;专属数据库已打开 撤销派生的 KEK/DEK 句柄、关闭已打开资源并回到 Locked
Unlocked → Locking → Locked 原子地关闭新操作准入,并等待在途操作结束或取消 无可用受管 DEK 句柄或数据库连接;明文缓存、临时文件与令牌已尽力清理 保持拒绝访问并进入 Error,记录不含明文的诊断,重试清理直到恢复不变量
Unlocked → Error 密文认证失败、数据库打开失败或不变量被破坏 立即拒绝读写,绝不回退到普通笔记本路径 关闭资源,诊断信息不得含明文;用户只能锁定或重新解锁
改主密码 所有加密笔记本均为 Locked 所有 WrappedDEK、全局配置和备份作为一个可恢复事务更新 保留旧配置和备份可用;不得出现仅能用新密码解锁的部分笔记本

并发规则:全局配置锁必须先于任何笔记本生命周期锁获取;同时涉及多个笔记本时按 boxID 字典序获取生命周期锁;DEK 缓存、数据库、文件和其他子系统锁只能在生命周期锁之后获取。每个笔记本使用同一个生命周期控制器管理状态、操作准入和活跃操作计数——读取、编辑、导出、预览、同步、索引、历史查看或恢复、AI/MCP 访问只能在 Unlocked 时取得生命周期租约,并持有到解析完成、缓存发布、HTTP 响应或下载结束。锁定与删除原子地把状态改为 Locking 并关闭新租约准入,取得排他租约后在排他租约内关闭数据库、清理缓存并撤销受管 DEK 句柄;只有清理成功才发布 Locked。锁定完成后,不得再出现晚到的明文响应、缓存回填、临时文件或数据库重连。

17. 密文、密钥与数据库格式

持久化格式分层,全局 KDF 配置、密钥包络和每个数据对象的封装互不混同:

  1. 数据对象封装:每个 .sy、资源、资源名称映射和数据库定义对象至少包含格式版本、算法标识、密码学安全随机生成且对同一用途密钥唯一的 nonce、密文和认证标签。当前格式的 AAD 按 格式版本 + boxID + 对象类型 + 稳定对象 ID 确定性构造,不包含父目录或绝对路径;.sy 的稳定对象 ID 是规范化文件基名 <rootID>.sy,资源使用脱敏磁盘文件基名,数据库定义使用 avID,固定单例文件使用版本化常量 ID。解密后必须验证对象内部 ID 与 AAD 对象 ID 一致,并拒绝同一笔记本内的重复对象 ID。AES-GCM nonce 由操作系统 CSPRNG 生成(见 kernel/util/kdf.gorandomBytes,随机源失败必须终止写入);认证标签不能事后识别已经发生的 nonce 重用。
  2. 全局密钥与包络元数据NotebookCrypto 保存 KDF 算法、参数、MasterSalt、verifier 格式、KEK 包络版本和备份 HMAC 版本;BoxConf.WrappedDEK 保存每个笔记本的 DEK 包络。它们不随每个数据对象重复保存。
  3. 用途隔离:每个笔记本的 DEK 通过固定域分隔符(HKDF-SHA256,见 kernel/util/kdf.goDeriveSubKey)派生文件内容、资源、数据库定义、content SQLCipher 和 blocktree SQLCipher 子密钥。资源内容与资源名称映射可以共用资源子密钥(版本化 AAD 对象类型不同);KEK 可直接用于 verifier、DEK 包络和备份 HMAC,各用途由不同算法、版本化 AAD 或被认证的数据格式隔离。
  4. 数据库兼容信息:SQLCipher 版本、cipher 参数和 schema 版本保存在数据库头部可验证位置或对应的加密配置边界。content 与 blocktree 索引可重建;源密文认证通过后,版本不兼容的索引应关闭并重建,而不是降低 cipher 参数或回退到明文 SQLite。SQLCipher 的主数据库、WAL、SHM、回滚日志、临时文件和备份副本都是受保护对象,未加密 SQLite 页面、查询结果或诊断转储不得写入全局临时目录。
  5. 改名与格式稳定性:首个发布格式直接以稳定对象 ID 构造 AAD,不兼容开发阶段按完整逻辑相对路径生成的试验密文,也不提供双格式读取或迁移。只改变父目录不重新封装内容;稳定文件基名变化时才使用旧基名认证解密并按新基名生成新封装。首个发布版本冻结后,未来再修改信封或 AAD 语义必须提升格式版本并另行设计迁移(NotebookCrypto.UpgradeSpec 预留了逐版本升级入口)。

18. 备份、恢复与元数据策略

notebook-crypto-backup.json 是恢复材料而非秘密。文件带格式版本、备份 ID、创建时间、内容摘要(Checksum)和基于 KEK 的 HMAC(KEKMAC);主密码派生 KEK 后,HMAC 缺失或不匹配必须拒绝恢复,不得作为兼容路径放行。恢复不得静默覆盖已启用配置。格式无效或与已有加密笔记本不匹配的备份进入错误状态,而不是生成新的 MasterSalt。

备份持久化遵循项目现有文件写入语义:全局备份通过同目录随机临时文件写入后原子替换(atomicWriteFile),每个笔记本的 notebook-crypt-backup.json 使用文件锁保护的常规配置写入(filelock.WriteFile)。两者都不额外承诺断电场景下的文件或目录 fsync 持久性。启动时校验无需密钥即可验证的结构和摘要;输入主密码后的恢复再校验 HMAC 以及密钥包络匹配关系(verifyKEKAgainstExistingBoxes 会用 KEK 对所有现有加密笔记本的 WrappedDEK 做无副作用解密校验,优先 conf 的 WrappedDEK,失败时 fallback 到 backup)。

重要:HMAC 只能证明备份内容未被不知道 KEK 的一方篡改,不能证明它是最新版本。当前范围不引入可信外部单调计数器,因此一个认证有效的历史备份或历史密文可能通过校验;BackupIDCreatedAt 只是供诊断和人工比较的信息,不是防回滚锚点。真正防回滚需要独立可信状态,超出本设计范围。

元数据暴露策略

类别 是否可泄露
文档正文、属性值、资源原始名称、数据库单元格、索引文本
boxID、目录结构、文件数量、密文大小、修改时间、资源扩展名、块 ID 时间信息
笔记本名称 是。笔记本列表与加密笔记本状态 API 在锁定时保留名称,以便用户识别要解锁的笔记本,因此笔记本名称不属于机密性保护边界
图标、排序、文档标题和数量、关系数量、标签、书签、历史与快照名称 锁定时默认不得暴露;若兼容性要求暴露,必须逐项说明

19. 临时明文与接口规则:拒绝访问登记

所有可能绕过专用读写路径的原始入口必须登记,包括文件 API、内核 CLI、MCP 文件工具、WebDAV、插件文件接口、导出下载 URL、预览 URL 和后台任务。登记表同时覆盖加密笔记本目录和内核管理的明文临时根目录;临时目录必须位于通用原始文件命名空间之外,或被所有原始文件 API 无条件拒绝,不能依赖当前笔记本是否仍存在或 Encrypted 标志是否仍可查询。

当前实现状态:文档中描述的持久化 encrypted-deny-access.json 注册表与周期性清理 janitor 尚未实现。当前版本依赖(1)内存态导出令牌(managedEncryptedExports,1 小时过期,锁定或删除笔记本时撤销);(2)WebDAV 层对整个 temp/ 目录的无条件拒绝。内核崩溃后 temp/export/ 残留明文暂无持久化追踪与重试清理,属已知限制。

目标设计:拒绝记录持久化在 <ConfDir>/encrypted-deny-access.json(位于 DataDir 和 dejavu 同步范围之外),每项记录包含格式版本、随机产物 ID、boxID、受控根目录下的规范相对路径、用途、创建时间、重试信息和不含原始内容的错误类别。文件 API、下载、预览、插件、MCP、WebDAV 和后台任务按规范路径无条件拒绝整个受控明文临时命名空间,即使笔记本或记录文件已不存在。清理器在应用启动、锁定或删除、导出结束或失败后运行,周期性按有上限的指数退避重试;清理成功并确认路径不存在后原子删除记录,持续失败则保留记录并维持拒绝状态,不得因超过重试次数自动放行。记录文件缺失或损坏时访问仍保持拒绝。

导出分为两类:用户主动选择的外部目的地允许产生明文(开始前提示风险);内核管理的预览、转换、下载和中间产物只能位于带 boxID 的受控临时目录,并通过绑定用户、boxID、用途和有效期的最小权限令牌访问。锁定、取消、失败、应用退出和导出完成后都使令牌与异步结果立即失效,再尽力清理明文。

20. 安全验收矩阵(关键场景速查)

场景 预期结果
锁定后经 UI、HTTP、文件 API、CLI、MCP、WebDAV、插件、后台任务访问 除明确允许暴露的笔记本名称外,不能读取、写入、复制、删除或列举加密内容;错误不返回明文、密文或可推断文档标题的信息
解锁、锁定、应用重启和认证失败 只有 Unlocked 才能取得受管 DEK 句柄和数据库连接;失败和重启后入口拒绝访问,受控明文缓存、临时文件与句柄完成尽力清理
锁定与响应生成、缓存发布、导出、预览、同步、索引或历史恢复并发 锁定在所有在途生命周期租约结束后才成功;成功后无晚到的明文响应、缓存回填、临时文件、可用令牌或数据库重连
跨边界引用、移动、镜像、资源和数据库操作 孤岛边界保持;拒绝时不产生半完成文件、全局索引或关联数据
资源密文写入与名称映射更新任一步失败 整个逻辑事务失败并回滚;不留下可访问的孤儿资源、缺失映射或明文临时文件
密文篡改、路径替换和备份损坏 认证或格式校验失败并安全拒绝;不回退普通路径、不生成新密钥材料、不静默覆盖配置
nonce 随机源失败或达到用途密钥调用限制 在加密前拒绝写入;发现 nonce 重用按密钥泄露事件处理,轮换 DEK 后重新加密
回放认证有效的历史密文或密钥备份 可能被识别为有效旧版本,但不得被宣称为最新或来源可信;当前设计不声称提供回滚防护
改主密码和 KEK 重新包络 WrappedDEK 与备份原子更新;更新中断后旧配置仍可恢复使用;迁移前后数据与历史均可验证读取
多个笔记本并发操作与自动锁定 锁顺序无死锁;认证用户界面活动或显式保活统一刷新所有当前已解锁笔记本,后台任务不自动刷新

21. 使用指南

21.1 首次启用

  1. 进入 设置 → 访问授权 → 加密笔记本,打开开关。
  2. 设置主密码(双输入确认 + 风险确认)。主密码是所有加密笔记本的统一钥匙,必须牢记——没有找回后门
  3. 启用后即可在文件面板"更多"菜单里 新建加密笔记本(输入名字 + 主密码 → 自动解锁并打开)。

强度建议:12 位以上,混合大小写 + 数字 + 符号。主密码越强,抗暴力破解能力越高(密码强度是唯一的安全防线,没有后门)。

21.2 日常使用:解锁与锁定

  • 解锁:点击已关闭的加密笔记本 → 输主密码 → 等约 1 秒(Argon2id 派生)→ 打开。解锁仅作用于该笔记本,其他加密笔记本保持锁定。
  • 锁定:关闭该笔记本即等于锁定。内核停止新访问,等待或取消在途任务,撤销受管 DEK 句柄和数据库连接,并尽力清理明文缓存、临时文件与令牌。用完即锁是最重要的安全习惯。
  • 重启后:所有加密笔记本强制关闭,需重新输主密码解锁(受管 DEK 句柄只存在于进程内存,重启后需重新派生)。

重要:加密笔记本在锁定状态下提供最强的应用级保护,但锁定只会尽力清理应用可控的内存和临时数据,不承诺抹除操作系统或底层介质中的所有瞬时副本。解锁期间,已认证的应用内调用方(API、插件、AI/LLM 含 MCP)都能像读普通笔记本一样读到明文;内核 CLI 始终拒绝操作加密笔记本。

21.3 修改主密码

进入 设置 → 访问授权 → 修改主密码。改密只需重新包络各笔记本的 WrappedDEK,不重新加密文档数据,因此即时完成。改密后密钥备份会自动刷新并同步。

重要语义:改密采用 KEK 包络模式——DEK 本身不变,只是用新主密码派生的新 KEK 重新包络 WrappedDEK。这意味着改密不能吊销旧主密码的解密能力:如果旧主密码和旧 WrappedDEK(如同步端、备份、历史快照中保留的)同时泄露,仍能解出同一个 DEK,进而解当前数据。如果怀疑旧主密码已泄露,应迁移内容到新建的加密笔记本(新 DEK)而非仅改密。

21.4 多设备同步

加密笔记本的密文 .sy/assets/数据库文件会随数据同步(密文进密文出,闭环自洽);全局密钥材料(MasterSalt 等)也会自动备份到同步目录。新设备同步后无需手动"启用"

  1. 在新设备配置好相同的同步账号并完成同步。
  2. 同步会把密钥备份拉到本地,内核自动恢复"已启用"状态。
  3. 直接点击加密笔记本,输入主密码即可解锁使用。

若新设备同步后笔记本仍显示锁定属正常——点开输主密码即可。同步恢复可以校验备份完整性,但不能证明它是最新版本;出现多个历史副本时,系统无法自动判定新鲜度,恢复前应由用户根据可信来源和时间信息确认版本。

21.5 导入与导出

  • 导入:支持导入 .sy.zip 和 Markdown,内容在落盘前自动用 DEK 加密,与手动创建的文档一致。
  • 导出:与普通笔记本一致(需先解锁,导出明文)。.sy.zip、HTML、Word、PDF、Markdown 等格式均支持;锁定状态下拒绝导出。

21.6 忘记密码 / 密钥备份丢失怎么办

忘记密码无法恢复——这是设计使然(没有后门)。即使密文已同步到云端,没有主密码也解不开。务必牢记主密码,建议使用密码管理器保存。

conf/conf.json 与同步目录里的密钥备份同时丢失(极端情况),重新启用加密笔记本会被拒绝并提示恢复备份文件。只要能从其他已同步的设备或之前导出的密钥文件找回 notebook-crypto-backup.json,可通过未启用状态下显示的"导入密钥"按钮导入,或手动放回 <工作区>/data/.siyuan/ 后重新启用,即可用该密钥对应的主密码解锁。恢复已删除加密笔记本的历史同样需要与其 WrappedDEK 匹配的全局密钥备份和主密码。

21.7 适用场景建议

适合:隐私日记、财务记录、医疗信息;工作机密、商业方案、合同;担心设备遗失或被盗的场景(加密后即使磁盘数据被恢复,没有主密码也只是密文)。

不适合:日常笔记、学习笔记(加密的额外开销不值得);需要全局搜索的内容(加密笔记本不参与);需要跨笔记本块引的知识网络、跨笔记本移动整理、跨笔记本镜像数据库的场景(均禁止跨边界);需要闪卡复习、书签/标签管理的内容(不支持);不希望 AI/LLM 接触的内容(解锁后 AI 可读;若介意,需保持锁定);大量大文件(assets 每次浏览都全量解密)。

日常使用习惯

  1. 主密码必须牢记:忘记密码 = 数据永久不可恢复(没有后门)。
  2. 用完即锁:减少 DEK 在内存的暴露时间。
  3. 不要把所有笔记都放加密笔记本:只放真正敏感的。
  4. 主密码强度:建议 12 位以上,混合大小写 + 数字 + 符号。
  5. 备份:分别保留版本化的密文备份和匹配的密钥备份;主密码或全部匹配密钥备份丢失后,密文无法恢复。

结语

SiYuan 的加密笔记本通过"主密码 → Argon2id → KEK → AES-256-GCM 包络 → 每笔记本独立 DEK"的分层密钥架构,结合每笔记本独立的 SQLCipher SQLite 数据库与"孤岛式"的跨边界防护,实现了对静态数据的强加密与锁定后的不可达性。其核心心智模型只有一条:加密笔记本是孤岛,敏感内容用完即锁。理解这一模型,你就能在享受本地加密保护的同时,正确规避闪卡、书签、标签、跨边界引用等特性限制,把加密能力用在真正需要它的场景上。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
900
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
927
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.89 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
602
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
396
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
526