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

  1. 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.
  2. 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.
  3. 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.
  4. 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

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


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/cf16668893469bfc. Report an issue: GitHub.