{"record":{"id":"17dd0b0fefe6fade","repo":"siyuan-note/siyuan","slug":"encrypted-notebook-has-no-valid-key-material","errorCode":null,"errorMessage":"encrypted notebook has no valid key material","messagePattern":"encrypted notebook has no valid key material","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"kernel/model/crypto.go","lineNumber":1921,"sourceCode":"\t// conf 中有完整的 BoxCrypt\n\tif confMarkedEncrypted && boxConf.BoxCrypt != nil && len(boxConf.BoxCrypt.WrappedDEK) > 0 {\n\t\treturn boxConf.BoxCrypt, nil\n\t}\n\n\t// fallback 到 backup\n\tbackup, err := readNotebookCryptBackup(boxID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif backup != nil && len(backup.WrappedDEK) > 0 {\n\t\tmarkRuntimeEncryptedBox(boxID)\n\t\treturn backup, nil\n\t}\n\n\t// backup 也不可用\n\tif confMarkedEncrypted || IsEncryptedBox(boxID) {\n\t\t// conf 标记为加密但密钥材料缺失 → 明确错误（而非误报\"未加密\"）\n\t\treturn nil, errors.New(\"encrypted notebook has no valid key material\")\n\t}\n\treturn nil, nil // 真正的非加密笔记本\n}\n\n// needWriteNotebookCryptBackup 检查是否需要写入/刷新 per-notebook backup。\n// backup 不存在、或内容与 crypt 不一致时返回 true。\nfunc needWriteNotebookCryptBackup(boxID string, crypt *conf.BoxEncryption) bool {\n\texisting, err := readNotebookCryptBackup(boxID)\n\tif err != nil || existing == nil {\n\t\treturn true\n\t}\n\treturn !bytes.Equal(existing.WrappedDEK, crypt.WrappedDEK) ||\n\t\t!bytes.Equal(existing.WrapNonce, crypt.WrapNonce) ||\n\t\t!bytes.Equal(existing.Metadata, crypt.Metadata) ||\n\t\texisting.Spec != crypt.Spec ||\n\t\texisting.CreatedAt != crypt.CreatedAt\n}\n","sourceCodeStart":1903,"sourceCodeEnd":1939,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/crypto.go#L1903-L1939","documentation":"Thrown by GetBoxEncryption when the notebook is identified as encrypted (conf.json marks it Encrypted=true, or IsEncryptedBox detects encryption markers) but no usable key material exists: conf.json's BoxCrypt is nil/empty AND the per-notebook crypt backup is missing or has no WrappedDEK. This is a data-loss condition — the notebook is encrypted but its key is unrecoverable from any local source.","triggerScenarios":"Called via the API layer (api/notebook.go:654) during unlock, or from ChangeMasterPassword. Fires when conf.json has Encrypted=true but BoxCrypt was deleted/corrupted, and the backup file is also gone. IsEncryptedBox may detect encryption through ciphertext signatures in the notebook data even when conf is damaged.","commonSituations":"conf.json was manually edited and the BoxCrypt section was deleted. A sync conflict dropped both conf BoxCrypt and the backup. The notebook directory was partially copied/restored without the .siyuan/conf.json or backup files. A disk failure corrupted both key sources. The notebook was imported without its key material.","solutions":["Restore from a DataDir backup that contains a valid conf.json with BoxCrypt or a per-notebook crypt backup for this notebook.","If sync is enabled, wait for a sync from another device that has the valid key material (both conf and backup sync with the workspace).","If a master-password migration manifest exists, restart SiYuan — recoverMasterPasswordMigration may rebuild BoxCrypt from the manifest's NewWrappedDEK.","If no key material exists anywhere, the notebook's encrypted content (.sy files, assets, AV data) is permanently unrecoverable."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Check key material availability before unlocking:\nboxCrypt, err := model.GetBoxEncryption(boxID)\nif err != nil {\n    // if err is 'encrypted notebook has no valid key material',\n    // the notebook is unrecoverable without a backup\n    return\n}\nif boxCrypt == nil {\n    // not encrypted — no unlock needed\n    return\n}","typeGuard":null,"tryCatchPattern":"boxCrypt, err := model.GetBoxEncryption(boxID)\nif err != nil {\n    if strings.Contains(err.Error(), \"no valid key material\") {\n        // critical: key material is lost\n        // advise user to restore from DataDir backup or sync from another device\n    }\n    return\n}","preventionTips":["Keep regular DataDir backups that include both conf.json and per-notebook crypt backups.","Enable sync so key material propagates to at least one other device.","Never delete the .siyuan/conf.json or backup files from an encrypted notebook directory.","Resolve sync conflicts by keeping the complete BoxCrypt from one device."],"tags":["encryption","key-loss","data-loss","config-corruption","go"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}