siyuan-note/siyuan · error

no encrypted key material for box [%s]

Error message

no encrypted key material for box [%s]

What it means

decryptBoxCrypt in kernel/model/crypto.go throws this when the notebook's BoxEncryption config is missing, nil, or has an empty WrappedDEK field. The WrappedDEK is the envelope-encrypted data encryption key; without it there is no key material to unwrap with the KEK, so decryption cannot even be attempted. This is a configuration/state problem, not a wrong-password problem.

Source

Thrown at kernel/model/crypto.go:1324

			return nil, errMasterPasswordMigrationPending
		}
		if err = saveNotebookCryptoBackup(kek); err != nil {
			zeroAndClear(kek)
			return nil, fmt.Errorf("%w: %v", errMasterPasswordMigrationPending, err)
		}
		removeMasterPasswordMigration()
	}
	return kek, nil
}

// decryptBoxCrypt 用 KEK 解密 box 的 WrappedDEK。优先使用 GetBoxEncryption 的结果(conf → backup fallback),
// 若解密失败则尝试 backup 中不同的 WrappedDEK。
// 返回解密后的 DEK 和实际使用的 BoxCrypt(可能来自 backup)。
// 若 backup 被使用会自动修复 conf.json 和刷新 backup。
func decryptBoxCrypt(boxID string, kek []byte) (dek []byte, boxCrypt *conf.BoxEncryption, err error) {
	boxCrypt, err = GetBoxEncryption(boxID)
	if err != nil || boxCrypt == nil || len(boxCrypt.WrappedDEK) == 0 {
		return nil, nil, fmt.Errorf("no encrypted key material for box [%s]", boxID)
	}

	nc := currentNotebookCrypto()
	dek, err = decryptWrappedDEKWithHistory(boxID, boxCrypt, kek, nc)
	if err == nil {
		return dek, boxCrypt, nil
	}

	// 主 BoxCrypt 无法解密:尝试 backup 中不同的 WrappedDEK
	backup, bErr := readNotebookCryptBackup(boxID)
	if bErr == nil && backup != nil && len(backup.WrappedDEK) > 0 &&
		!bytes.Equal(backup.WrappedDEK, boxCrypt.WrappedDEK) {
		dek, err = decryptWrappedDEKWithHistory(boxID, backup, kek, nc)
		if err == nil {
			// backup 解密成功:修复 conf + 刷新 backup
			box := &Box{ID: boxID}
			boxConf := box.GetConf()
			boxConf.Encrypted = true

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Verify the notebook is actually encrypted and that conf.json contains a BoxEncryption object with a non-empty WrappedDEK
  2. If conf.json is corrupted or stale, restore it from a trusted sync/backup snapshot rather than recreating the notebook
  3. If the notebook should not be encrypted, stop calling the unlock/change-password API on it and check encryption state first
  4. Re-enable notebook encryption through the UI so a fresh key envelope is generated, then retry unlock

Example fix

// before
boxCrypt, _ := GetBoxEncryption(boxID)
dek, _, err := decryptBoxCrypt(boxID, kek)

// after
boxCrypt, err := GetBoxEncryption(boxID)
if err != nil || boxCrypt == nil || len(boxCrypt.WrappedDEK) == 0 {
    return fmt.Errorf("box [%s] has no encrypted key envelope; check conf.json or enable encryption", boxID)
}
dek, _, err := decryptBoxCrypt(boxID, kek)
Defensive patterns

Strategy: validation

Validate before calling

// Go
crypt, err := model.GetBoxEncryption(boxID)
if err != nil || crypt == nil || len(crypt.WrappedDEK) == 0 {
    return fmt.Errorf("box %s has no key envelope; skipping unlock", boxID)
}
// then call the unlock path

Type guard

func hasKeyEnvelope(c *conf.BoxEncryption) bool {
    return c != nil && len(c.WrappedDEK) > 0
}

Prevention

When it happens

Trigger: GetBoxEncryption(boxID) returns an error, a nil boxCrypt, or a boxCrypt with len(WrappedDEK)==0. Reached via UnlockBox -> unlockBoxHeld, ChangeMasterPassword, or the recovery test path when the box has no stored encrypted key envelope.

Common situations: conf.json was manually edited or truncated and lost the BoxCrypt section; the notebook was created before encryption was enabled so no envelope exists; a sync/backup restored an older conf.json; the box ID passed does not correspond to an encrypted notebook.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/d0182522d76487cf. Report an issue: GitHub.