siyuan-note/siyuan · critical

310

310

Error message

Encrypted notebook feature is not enabled

What it means

Thrown by deriveNotebookCryptoBackupCandidate (crypto.go:1172, i18n code 310) when the global key backup is unusable as a recovery source: loadNotebookCryptoBackup errored, or returned nil, or its MasterSalt/KEKVerifier fields are empty. This candidate function is called during recovery (new-device sync, re-enable, deriveKEK fallback), so an absent backup means there is no key material to verify the password against. The message 'Encrypted notebook feature is not enabled' reflects that no authenticated crypto config is available.

Source

Thrown at kernel/model/crypto.go:1172

	*Conf.NotebookCrypto = *backup
	Conf.m.Unlock()
	Conf.Save()
	// 恢复成功后同步重写备份,确保配置和备份内容一致。
	// 调用方已持有 notebookCryptoMu,且 writeNotebookCryptoBackupData 不再申请该锁,故无死锁;
	// 同步写避免与 ChangeMasterPassword 的并发备份写竞争同一文件(lost update 导致 verifier 被回退)。
	nc := *backup
	if err := writeNotebookCryptoBackupData(&nc, kek); err != nil {
		logging.LogWarnf("rewrite notebook crypto backup after restore failed: %s", err)
	}
	logging.LogInfof("notebook crypto restored from backup (e.g. after sync to a new device)")
	return kek, nil
}

// deriveNotebookCryptoBackupCandidate 对同步备份做无副作用验证,并确认它覆盖全部现有加密笔记本。
func deriveNotebookCryptoBackupCandidate(password string) (backup *conf.NotebookCrypto, kek []byte, err error) {
	backup, err = loadNotebookCryptoBackup()
	if err != nil || backup == nil || len(backup.MasterSalt) == 0 || len(backup.KEKVerifier) == 0 {
		return nil, nil, errors.New(Conf.Language(310))
	}
	params, validErr := util.ValidateArgon2Params(backup.KDFParams)
	if validErr != nil {
		return nil, nil, errors.New(Conf.Language(317))
	}
	kek = util.DeriveKey(password, backup.MasterSalt, params)
	decrypted, decryptErr := util.DecryptWithAAD(kek, backup.KEKVerifier, []byte("siyuan:kek-verifier"))
	if decryptErr != nil || string(decrypted) != string(kekVerifierMagic) {
		zeroAndClear(kek)
		return nil, nil, errors.New(Conf.Language(311))
	}
	if backup.Spec != conf.CurrentNotebookCryptoSpec || backup.Checksum == "" ||
		len(backup.KEKMAC) == 0 || !verifyKEKMAC(backup, kek) {
		zeroAndClear(kek)
		return nil, nil, errors.New(Conf.Language(316))
	}
	if !verifyKEKAgainstExistingBoxes(kek) || !verifyKEKAgainstEncryptedHistory(kek) {
		zeroAndClear(kek)

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Restore the key backup file (dataCryptoBackupPath()) from a sync snapshot, external copy, or another device that has it, then retry.
  2. If the local conf.json still has MasterSalt/KEKVerifier, rely on the local-config auth path instead (ensure conf is not marked Enabled=false so deriveKEK uses it), or re-enable via conf recovery.
  3. If no key material exists anywhere and the encrypted data is expendable, remove the orphaned encrypted notebooks/history so the key-domain check no longer blocks a fresh enable.
Defensive patterns

Strategy: validation

Validate before calling

// Before relying on backup recovery, confirm the backup is present and populated.
func backupUsable() error {
    if !filelock.IsExist(dataCryptoBackupPath()) {
        return errors.New("key backup file missing; restore it before recovery")
    }
    b, err := loadNotebookCryptoBackup()
    if err != nil || b == nil || len(b.MasterSalt) == 0 || len(b.KEKVerifier) == 0 {
        return errors.New("key backup incomplete; restore a full backup")
    }
    return nil
}

Try / catch

if err := model.EnableEncryptedNotebook(password); err != nil {
    if err.Error() == model.Conf.Language(310) {
        respond(c, "no usable key backup on this device; restore the backup or sync it here")
        return
    }
    respond(c, err.Error())
}

Prevention

When it happens

Trigger: Reached when: (a) EnableEncryptedNotebook finds a key domain and calls tryRestoreNotebookCryptoFromBackupLocked -> deriveNotebookCryptoBackupCandidate, or (b) deriveKEK with local config disabled/invalid tries the backup. The backup file at dataCryptoBackupPath() is missing, deleted, unreadable, or lacks MasterSalt/KEKVerifier.

Common situations: Sync delivered encrypted notebooks but the key backup file did not arrive or was deleted. Backup was manually removed. A partially-written backup after a crash left it JSON-valid but missing key fields.

Related errors


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