siyuan-note/siyuan · critical
315
315
Error message
Encrypted notebooks already exist but the master key backup is missing. Restore the original conf.json or backup file to re-enable
What it means
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.
Source
Thrown at kernel/model/crypto.go:1008
if listErr != nil {
return fmt.Errorf("list encrypted notebooks failed: %w", listErr)
}
hasHistory, historyErr := scanEncryptedNotebookHistory()
if historyErr != nil {
return fmt.Errorf("check encrypted notebook history failed: %w", historyErr)
}
hasBackup := filelock.IsExist(dataCryptoBackupPath())
if hasEncrypted || hasHistory || hasBackup {
// 现存笔记本、已删除笔记本历史或全局备份均表示已有密钥域,必须恢复并认证,不能生成新 MasterSalt。
kek, restoreErr := tryRestoreNotebookCryptoFromBackupLocked(password)
if kek != nil {
zeroAndClear(kek)
}
if restoreErr != nil {
if strings.Contains(restoreErr.Error(), Conf.Language(311)) {
return errors.New(Conf.Language(311))
}
return errors.New(Conf.Language(315))
}
logging.LogInfof("encrypted notebook re-enabled with authenticated recovery key material")
return nil
}
// 不存在任何密钥依赖或备份时生成新的 MasterSalt。
salt, err := util.GenerateSalt()
if err != nil {
return err
}
Conf.m.RLock()
kdfParams := Conf.NotebookCrypto.KDFParams
Conf.m.RUnlock()
params, validErr := util.ValidateArgon2Params(kdfParams)
if validErr != nil {
return validErr
}
kek := util.DeriveKey(password, salt, params)View on GitHub (pinned to 251596fc0d)
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.
Defensive patterns
Strategy: validation
Validate before calling
// Before calling EnableEncryptedNotebook, verify a restorable key domain is intact.
func canRecoverKeyDomain() error {
if has, err := model.HasEncryptedNotebook(); err != nil {
return err
} else if !has {
if hist, _ := model.HasEncryptedNotebookHistory(); !hist && !filelock.IsExist(dataCryptoBackupPath()) {
return nil // fresh enable is safe
}
}
// key domain exists: ensure conf or backup carries valid key material
nc := model.Conf.NotebookCrypto
if len(nc.MasterSalt) == 0 || len(nc.KEKVerifier) == 0 {
if !filelock.IsExist(dataCryptoBackupPath()) {
return errors.New("key domain exists but conf and backup are both missing key material; restore conf.json or backup")
}
}
return nil
} Try / catch
if err := model.EnableEncryptedNotebookWithSync(password); err != nil {
if strings.Contains(err.Error(), model.Conf.Language(315)) {
// guide user to restore conf.json or the key backup file before retrying
respond(c, "restore your conf.json or key backup, then re-enable")
return
}
respond(c, err.Error())
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- 310
- no encrypted key material for box
- enable encrypted notebook failed: failed to persist key back
- cannot disable encrypted notebook feature while encrypted no
- 323
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/cf16668893469bfc.
Report an issue: GitHub.