{"record":{"id":"cf16668893469bfc","repo":"siyuan-note/siyuan","slug":"315","errorCode":"315","errorMessage":"Encrypted notebooks already exist but the master key backup is missing. Restore the original conf.json or backup file to re-enable","messagePattern":"Encrypted notebooks already exist but the master key backup is missing\\. Restore the original conf\\.json or backup file to re-enable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"kernel/model/crypto.go","lineNumber":1008,"sourceCode":"\tif listErr != nil {\n\t\treturn fmt.Errorf(\"list encrypted notebooks failed: %w\", listErr)\n\t}\n\thasHistory, historyErr := scanEncryptedNotebookHistory()\n\tif historyErr != nil {\n\t\treturn fmt.Errorf(\"check encrypted notebook history failed: %w\", historyErr)\n\t}\n\thasBackup := filelock.IsExist(dataCryptoBackupPath())\n\tif hasEncrypted || hasHistory || hasBackup {\n\t\t// 现存笔记本、已删除笔记本历史或全局备份均表示已有密钥域，必须恢复并认证，不能生成新 MasterSalt。\n\t\tkek, restoreErr := tryRestoreNotebookCryptoFromBackupLocked(password)\n\t\tif kek != nil {\n\t\t\tzeroAndClear(kek)\n\t\t}\n\t\tif restoreErr != nil {\n\t\t\tif strings.Contains(restoreErr.Error(), Conf.Language(311)) {\n\t\t\t\treturn errors.New(Conf.Language(311))\n\t\t\t}\n\t\t\treturn errors.New(Conf.Language(315))\n\t\t}\n\t\tlogging.LogInfof(\"encrypted notebook re-enabled with authenticated recovery key material\")\n\t\treturn nil\n\t}\n\n\t// 不存在任何密钥依赖或备份时生成新的 MasterSalt。\n\tsalt, err := util.GenerateSalt()\n\tif err != nil {\n\t\treturn err\n\t}\n\tConf.m.RLock()\n\tkdfParams := Conf.NotebookCrypto.KDFParams\n\tConf.m.RUnlock()\n\tparams, validErr := util.ValidateArgon2Params(kdfParams)\n\tif validErr != nil {\n\t\treturn validErr\n\t}\n\tkek := util.DeriveKey(password, salt, params)","sourceCodeStart":990,"sourceCodeEnd":1026,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/crypto.go#L990-L1026","documentation":"Thrown by EnableEncryptedNotebook (crypto.go:1008, i18n code 315) when the data directory already contains an encrypted-notebook key domain (an encrypted notebook, deleted-notebook history, or a global key backup) but tryRestoreNotebookCryptoFromBackupLocked could not authenticate recovery key material against it. The function refuses to generate a fresh MasterSalt because doing so would orphan all existing WrappedDEK ciphertext, permanently locking every encrypted notebook and history snapshot. The only way forward is to restore the original conf.json or the key backup so the existing key material can be verified.","triggerScenarios":"Calling EnableEncryptedNotebookWithSync/EnableEncryptedNotebook (HTTP POST /api/notebook/enableEncryptedNotebooks) when hasEncrypted||hasHistory||hasBackup is true and tryRestoreNotebookCryptoFromBackupLocked returns a non-nil error whose message does not contain the 'Incorrect master password' (311) string. Concretely: deriveNotebookCryptoBackupCandidate failed because the backup's KEKMAC/spec/checksum was wrong, the KEK did not match existing boxes or encrypted history, or the backup KDF params were invalid.","commonSituations":"conf.json was hand-edited, truncated, or reset (e.g. after a crash or a partial sync) while encrypted notebooks and/or the history directory still reference the old key domain. Syncing data to a device where the backup arrived corrupt or the backup was deleted manually. Restoring a Data.zip that includes encrypted notebook directories but whose key backup file is missing or belongs to a different password.","solutions":["Restore the original conf.json from a backup (it carries MasterSalt/KEKVerifier/KDFParams) so the existing key domain can be authenticated, then retry enable with the correct master password.","If conf.json is unrecoverable, restore the global key backup file (dataCryptoBackupPath()) from a sync snapshot or external copy, then retry; deriveNotebookCryptoBackupCandidate will re-derive the KEK and rewrite conf.json.","If neither conf.json nor any key backup exists and the encrypted notebooks/history are no longer needed, you must explicitly delete the orphaned encrypted notebook directories and clear the encrypted history entries first, so the key-domain check passes and a new MasterSalt can be generated.","Verify the master password supplied to the enable call is the one originally used to create the key domain; a wrong password surfaces as 311 first, but a structurally-broken backup falls through to 315."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before calling EnableEncryptedNotebook, verify a restorable key domain is intact.\nfunc canRecoverKeyDomain() error {\n    if has, err := model.HasEncryptedNotebook(); err != nil {\n        return err\n    } else if !has {\n        if hist, _ := model.HasEncryptedNotebookHistory(); !hist && !filelock.IsExist(dataCryptoBackupPath()) {\n            return nil // fresh enable is safe\n        }\n    }\n    // key domain exists: ensure conf or backup carries valid key material\n    nc := model.Conf.NotebookCrypto\n    if len(nc.MasterSalt) == 0 || len(nc.KEKVerifier) == 0 {\n        if !filelock.IsExist(dataCryptoBackupPath()) {\n            return errors.New(\"key domain exists but conf and backup are both missing key material; restore conf.json or backup\")\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := model.EnableEncryptedNotebookWithSync(password); err != nil {\n    if strings.Contains(err.Error(), model.Conf.Language(315)) {\n        // guide user to restore conf.json or the key backup file before retrying\n        respond(c, \"restore your conf.json or key backup, then re-enable\")\n        return\n    }\n    respond(c, err.Error())\n}","preventionTips":["Never hand-edit or truncate conf.json on a workspace that has encrypted notebooks.","Keep the global key backup file synced/backed up externally so recovery is always possible.","After syncing to a new device, confirm the key backup arrived before attempting enable."],"tags":["encryption","notebook-crypto","key-recovery","config-corruption"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}