{"record":{"id":"614d25ad1cd5c6d1","repo":"siyuan-note/siyuan","slug":"cannot-generate-notebook-crypto-backup-without-kek","errorCode":null,"errorMessage":"cannot generate notebook crypto backup without KEK","messagePattern":"cannot generate notebook crypto backup without KEK","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":382,"sourceCode":"\tif err := writeNotebookCryptoBackupData(nc, kek); err != nil {\n\t\treturn fmt.Errorf(\"failed to persist key backup: %w\", err)\n\t}\n\tConf.m.Lock()\n\t*Conf.NotebookCrypto = *nc\n\tConf.m.Unlock()\n\tConf.Save()\n\tIncSync()\n\treturn nil\n}\n\n// saveNotebookCryptoBackup 把当前 NotebookCrypto（含 MasterSalt/KEKVerifier/KDFParams）备份到 DataDir。\n// kek 必须非 nil：在 Checksum 定型后计算 KEKMAC 并落盘，保证恢复路径可通过 MAC 校验。\n// 无 KEK 生成的备份 KEKMAC 必为空，会被 deriveKEK/恢复路径拒绝，等于制造无法解锁的状态（详见设计 §19）。\nfunc saveNotebookCryptoBackup(kek []byte) error {\n\tif kek == nil {\n\t\t// 无 KEK 时不得生成当前格式备份：KEKMAC 缺失会被 deriveKEK/恢复路径拒绝，\n\t\t// 生成即等于制造无法解锁的状态。\n\t\treturn errors.New(\"cannot generate notebook crypto backup without KEK\")\n\t}\n\tConf.m.Lock()\n\tnc := *Conf.NotebookCrypto // 值拷贝\n\tprepareBackupForWrite(&nc)\n\tnc.KEKMAC = computeKEKMAC(&nc, kek)\n\tif !notebookCryptoConfigurationComplete(&nc) {\n\t\tConf.m.Unlock()\n\t\treturn errors.New(\"cannot save incomplete notebook crypto configuration\")\n\t}\n\tConf.NotebookCrypto.Spec = nc.Spec\n\tConf.NotebookCrypto.BackupID = nc.BackupID\n\tConf.NotebookCrypto.CreatedAt = nc.CreatedAt\n\tConf.NotebookCrypto.Checksum = nc.Checksum\n\tConf.NotebookCrypto.KEKMAC = nc.KEKMAC // 保持 Conf 与备份文件的 KEKMAC 一致\n\tConf.m.Unlock()\n\tbackupPath := dataCryptoBackupPath()\n\tif err := os.MkdirAll(filepath.Dir(backupPath), 0755); err != nil {\n\t\treturn fmt.Errorf(\"mkdir notebook crypto backup dir failed: %w\", err)","sourceCodeStart":364,"sourceCodeEnd":400,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/crypto.go#L364-L400","documentation":"saveNotebookCryptoBackup nil-guard: it was invoked with a nil KEK. A backup written without a KEK has an empty KEKMAC and would be rejected by deriveKEK/recovery paths — creating an unrecoverable state — so generation is refused outright (design §19).","triggerScenarios":"Thrown at kernel/model/crypto.go:382 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Ensure the KEK is derived (password change/enable completed) before saving the backup","Retry the enabling/password-change operation that supplies the KEK","Never bypass the guard by writing a MAC-less backup"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}