siyuan-note/siyuan · critical
no encrypted key material for box
Error message
no encrypted key material for box [%s]
What it means
decryptBoxCrypt returns this error when the notebook's BoxEncryption record cannot be loaded, is nil, or has an empty WrappedDEK, meaning there is no encrypted key material to unwrap the notebook's DEK. Without a WrappedDEK the notebook cannot be decrypted with any password. It guards against attempting decryption on a notebook whose crypto configuration is missing or corrupt.
Solutions
- Check the notebook's conf.json contains an Encrypted flag and BoxCrypt.WrappedDEK; restore it from a backup if missing
- Verify the notebook crypto backup file exists and holds a WrappedDEK for this box so the auto-repair path can work
- Confirm you are unlocking a notebook that was actually encrypted; non-encrypted notebooks have no key material
- Re-sync the workspace from a peer that still has intact key material
Example fix
// before
model.UnlockBox(boxID, password, boxEnc) // boxEnc loaded from stale/partial conf
// after
if boxEnc == nil || len(boxEnc.WrappedDEK) == 0 {
return fmt.Errorf("box %s has no key material; restore conf.json or decrypt is impossible", boxID)
}
model.UnlockBox(boxID, password, boxEnc) Defensive patterns
Strategy: validation
Validate before calling
boxEnc, err := model.GetBoxEncryption(boxID)
if err != nil || boxEnc == nil || len(boxEnc.WrappedDEK) == 0 {
return fmt.Errorf("box %s has no encrypted key material; refusing unlock", boxID)
} Type guard
func hasKeyMaterial(b *conf.BoxEncryption) bool {
return b != nil && len(b.WrappedDEK) > 0
} Prevention
- Never hand-edit conf.json of encrypted notebooks
- Back up conf.json and the notebook crypto backup together
- Check Encrypted flag before treating a notebook as encrypted
- Verify sync completeness for crypto files
When it happens
Trigger: decryptBoxCrypt(boxID, kek) is called by unlockBoxHeld (via UnlockBox/UnlockAndMountBox), ChangeMasterPassword, or tests when GetBoxEncryption fails, or conf.json lacks BoxCrypt.WrappedDEK for the given notebook ID.
Common situations: Encrypted notebook's conf.json was edited/truncated or lost its BoxCrypt section; notebook was re-created or synced without its crypto config; callers passing a boxID of a non-encrypted notebook; backup file missing while conf is damaged.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- Conf.Language(316) + " [box=" + id + "]"
- decrypt box [ ] failed: incorrect key or corrupted data
- encrypted notebook has no valid key material
- invalid historical notebook encryption key
- no encrypted key material for box
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/d0182522d76487cf.
Report an issue: GitHub.
Appendix: 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 = trueView on GitHub (pinned to 9f775e8a12)