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
- Restore the key backup file (dataCryptoBackupPath()) from a sync snapshot, external copy, or another device that has it, then retry.
- 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.
- 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
- Always let the global key backup sync to every device.
- Keep an external copy of the key backup file.
- After Data.zip import, verify the backup file landed before unlocking.
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
- 315
- 317
- 316
- Encrypted notebooks already exist but the master key backup
- Cannot import a key backup while encrypted notebooks are ena
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/a6391cfb054f3fb2.
Report an issue: GitHub.