siyuan-note/siyuan · critical

encrypted notebook has no valid key material

Error message

encrypted notebook has no valid key material

What it means

Thrown by GetBoxEncryption when the notebook is identified as encrypted (conf.json marks it Encrypted=true, or IsEncryptedBox detects encryption markers) but no usable key material exists: conf.json's BoxCrypt is nil/empty AND the per-notebook crypt backup is missing or has no WrappedDEK. This is a data-loss condition — the notebook is encrypted but its key is unrecoverable from any local source.

Source

Thrown at kernel/model/crypto.go:1921

	// conf 中有完整的 BoxCrypt
	if confMarkedEncrypted && boxConf.BoxCrypt != nil && len(boxConf.BoxCrypt.WrappedDEK) > 0 {
		return boxConf.BoxCrypt, nil
	}

	// fallback 到 backup
	backup, err := readNotebookCryptBackup(boxID)
	if err != nil {
		return nil, err
	}
	if backup != nil && len(backup.WrappedDEK) > 0 {
		markRuntimeEncryptedBox(boxID)
		return backup, nil
	}

	// backup 也不可用
	if confMarkedEncrypted || IsEncryptedBox(boxID) {
		// conf 标记为加密但密钥材料缺失 → 明确错误(而非误报"未加密")
		return nil, errors.New("encrypted notebook has no valid key material")
	}
	return nil, nil // 真正的非加密笔记本
}

// needWriteNotebookCryptBackup 检查是否需要写入/刷新 per-notebook backup。
// backup 不存在、或内容与 crypt 不一致时返回 true。
func needWriteNotebookCryptBackup(boxID string, crypt *conf.BoxEncryption) bool {
	existing, err := readNotebookCryptBackup(boxID)
	if err != nil || existing == nil {
		return true
	}
	return !bytes.Equal(existing.WrappedDEK, crypt.WrappedDEK) ||
		!bytes.Equal(existing.WrapNonce, crypt.WrapNonce) ||
		!bytes.Equal(existing.Metadata, crypt.Metadata) ||
		existing.Spec != crypt.Spec ||
		existing.CreatedAt != crypt.CreatedAt
}

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Restore from a DataDir backup that contains a valid conf.json with BoxCrypt or a per-notebook crypt backup for this notebook.
  2. If sync is enabled, wait for a sync from another device that has the valid key material (both conf and backup sync with the workspace).
  3. If a master-password migration manifest exists, restart SiYuan — recoverMasterPasswordMigration may rebuild BoxCrypt from the manifest's NewWrappedDEK.
  4. If no key material exists anywhere, the notebook's encrypted content (.sy files, assets, AV data) is permanently unrecoverable.
Defensive patterns

Strategy: try-catch

Validate before calling

// Check key material availability before unlocking:
boxCrypt, err := model.GetBoxEncryption(boxID)
if err != nil {
    // if err is 'encrypted notebook has no valid key material',
    // the notebook is unrecoverable without a backup
    return
}
if boxCrypt == nil {
    // not encrypted — no unlock needed
    return
}

Try / catch

boxCrypt, err := model.GetBoxEncryption(boxID)
if err != nil {
    if strings.Contains(err.Error(), "no valid key material") {
        // critical: key material is lost
        // advise user to restore from DataDir backup or sync from another device
    }
    return
}

Prevention

When it happens

Trigger: Called via the API layer (api/notebook.go:654) during unlock, or from ChangeMasterPassword. Fires when conf.json has Encrypted=true but BoxCrypt was deleted/corrupted, and the backup file is also gone. IsEncryptedBox may detect encryption through ciphertext signatures in the notebook data even when conf is damaged.

Common situations: conf.json was manually edited and the BoxCrypt section was deleted. A sync conflict dropped both conf BoxCrypt and the backup. The notebook directory was partially copied/restored without the .siyuan/conf.json or backup files. A disk failure corrupted both key sources. The notebook was imported without its key material.

Related errors


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