siyuan-note/siyuan · critical

317

317

Error message

Invalid key backup file

What it means

Thrown by deriveNotebookCryptoBackupCandidate (crypto.go:1176, i18n code 317) when the backup exists and has key fields but its Argon2 KDF parameters fail validation (util.ValidateArgon2Params). Invalid KDF params mean the backup cannot safely derive the KEK, so it is rejected as an invalid key backup file rather than risking a weak or malformed key derivation.

Source

Thrown at kernel/model/crypto.go:1176

	// 调用方已持有 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)
		return nil, nil, errors.New(Conf.Language(316))
	}
	backup.KDFParams = params
	return backup, kek, nil

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Restore a backup with valid KDFParams from a known-good sync snapshot or another device, then retry.
  2. If only conf.json has valid KDFParams, prefer the local-config auth path (do not force the backup candidate path).
  3. As a last resort, if the encrypted data is expendable, remove orphaned notebooks/history and re-enable with a fresh key domain.
Defensive patterns

Strategy: validation

Validate before calling

// Confirm backup KDF params are valid before using it for recovery.
b, err := loadNotebookCryptoBackup()
if err != nil || b == nil {
    return errors.New("no backup")
}
if _, verr := util.ValidateArgon2Params(b.KDFParams); verr != nil {
    return fmt.Errorf("backup KDF params invalid; restore a compatible backup: %w", verr)
}

Try / catch

if err := model.EnableEncryptedNotebook(password); err != nil {
    if err.Error() == model.Conf.Language(317) {
        respond(c, "key backup has invalid KDF params; restore a spec-compatible backup")
        return
    }
    respond(c, err.Error())
}

Prevention

When it happens

Trigger: deriveNotebookCryptoBackupCandidate loads a backup whose KDFParams field is empty, malformed, or out of the allowed Argon2id bounds (memory, iterations, parallelism). Reached on the same recovery paths as 606: re-enable, new-device sync restore, or deriveKEK fallback.

Common situations: Backup written by an older/newer kernel version with different KDF param schema. Backup edited or corrupted. KDF params tampered with. Migration between Argon2 param formats.

Related errors


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