siyuan-note/siyuan · warning

312

312

Error message

Encrypted notebook feature is already enabled

What it means

EnableEncryptedNotebook returns Conf.Language(312) ('encrypted notebook feature is already enabled') when the current NotebookCrypto is both Enabled and structurally complete (notebookCryptoConfigurationComplete). The guard prevents a second enable from generating a fresh MasterSalt that would orphan existing WrappedDEKs. This fires before the hasEncrypted/hasHistory/hasBackup recovery path.

Source

Thrown at kernel/model/crypto.go:986

	return EnableEncryptedNotebook(password)
}

// EnableEncryptedNotebook 启用加密笔记本功能:生成 MasterSalt、派生 KEK、写入校验值并持久化。
// 重复调用(已启用)返回错误,避免覆盖现有加密笔记本的密钥参数。
// KEK 不缓存——启用后用户需对每个加密笔记本单独调 UnlockBox 解锁。
func EnableEncryptedNotebook(password string) error {
	if len(password) == 0 {
		return errors.New("password must not be empty")
	}

	notebookCryptoMu.Lock()
	defer notebookCryptoMu.Unlock()

	Conf.m.RLock()
	current := *Conf.NotebookCrypto
	Conf.m.RUnlock()
	if current.Enabled && notebookCryptoConfigurationComplete(&current) {
		return errors.New(Conf.Language(312))
	}

	hasEncrypted, listErr := hasEncryptedNotebook()
	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 {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Check NotebookCryptoEnabled() before offering the enable action in the UI.
  2. If the intent is to change the master password, use the change-password flow instead of re-enabling.
  3. If the configuration is corrupt-but-enabled, address the corruption directly rather than re-enabling.

Example fix

// before
err := model.EnableEncryptedNotebook(password)

// after
if model.NotebookCryptoEnabled() {
    // already enabled; route to change-password or unlock instead
    return
}
err := model.EnableEncryptedNotebook(password)
Defensive patterns

Strategy: validation

Validate before calling

// Check enabled state before offering the enable action.
if model.NotebookCryptoEnabled() {
    // route to unlock / change-password instead of enable
}

Prevention

When it happens

Trigger: EnableEncryptedNotebook (or EnableEncryptedNotebookWithSync falling through to it) is called while Conf.NotebookCrypto.Enabled is true and all required fields are present. Typical when the user clicks 'enable' on a device where encryption is already active.

Common situations: User double-invokes enable; UI state desync showing enable as available when it is already on; race where two enable requests are submitted; restored conf.json from a device where encryption was already enabled.

Related errors


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