{"record":{"id":"d0182522d76487cf","repo":"siyuan-note/siyuan","slug":"no-encrypted-key-material-for-box-s","errorCode":null,"errorMessage":"no encrypted key material for box [%s]","messagePattern":"no encrypted key material for box \\[(.+?)\\]","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":1324,"sourceCode":"\t\t\treturn nil, errMasterPasswordMigrationPending\n\t\t}\n\t\tif err = saveNotebookCryptoBackup(kek); err != nil {\n\t\t\tzeroAndClear(kek)\n\t\t\treturn nil, fmt.Errorf(\"%w: %v\", errMasterPasswordMigrationPending, err)\n\t\t}\n\t\tremoveMasterPasswordMigration()\n\t}\n\treturn kek, nil\n}\n\n// decryptBoxCrypt 用 KEK 解密 box 的 WrappedDEK。优先使用 GetBoxEncryption 的结果（conf → backup fallback），\n// 若解密失败则尝试 backup 中不同的 WrappedDEK。\n// 返回解密后的 DEK 和实际使用的 BoxCrypt（可能来自 backup）。\n// 若 backup 被使用会自动修复 conf.json 和刷新 backup。\nfunc decryptBoxCrypt(boxID string, kek []byte) (dek []byte, boxCrypt *conf.BoxEncryption, err error) {\n\tboxCrypt, err = GetBoxEncryption(boxID)\n\tif err != nil || boxCrypt == nil || len(boxCrypt.WrappedDEK) == 0 {\n\t\treturn nil, nil, fmt.Errorf(\"no encrypted key material for box [%s]\", boxID)\n\t}\n\n\tnc := currentNotebookCrypto()\n\tdek, err = decryptWrappedDEKWithHistory(boxID, boxCrypt, kek, nc)\n\tif err == nil {\n\t\treturn dek, boxCrypt, nil\n\t}\n\n\t// 主 BoxCrypt 无法解密：尝试 backup 中不同的 WrappedDEK\n\tbackup, bErr := readNotebookCryptBackup(boxID)\n\tif bErr == nil && backup != nil && len(backup.WrappedDEK) > 0 &&\n\t\t!bytes.Equal(backup.WrappedDEK, boxCrypt.WrappedDEK) {\n\t\tdek, err = decryptWrappedDEKWithHistory(boxID, backup, kek, nc)\n\t\tif err == nil {\n\t\t\t// backup 解密成功：修复 conf + 刷新 backup\n\t\t\tbox := &Box{ID: boxID}\n\t\t\tboxConf := box.GetConf()\n\t\t\tboxConf.Encrypted = true","sourceCodeStart":1306,"sourceCodeEnd":1342,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/crypto.go#L1306-L1342","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify the notebook is actually encrypted and that conf.json contains a BoxEncryption object with a non-empty WrappedDEK","If conf.json is corrupted or stale, restore it from a trusted sync/backup snapshot rather than recreating the notebook","If the notebook should not be encrypted, stop calling the unlock/change-password API on it and check encryption state first","Re-enable notebook encryption through the UI so a fresh key envelope is generated, then retry unlock"],"exampleFix":"// before\nboxCrypt, _ := GetBoxEncryption(boxID)\ndek, _, err := decryptBoxCrypt(boxID, kek)\n\n// after\nboxCrypt, err := GetBoxEncryption(boxID)\nif err != nil || boxCrypt == nil || len(boxCrypt.WrappedDEK) == 0 {\n    return fmt.Errorf(\"box [%s] has no encrypted key envelope; check conf.json or enable encryption\", boxID)\n}\ndek, _, err := decryptBoxCrypt(boxID, kek)","handlingStrategy":"validation","validationCode":"// Go\ncrypt, err := model.GetBoxEncryption(boxID)\nif err != nil || crypt == nil || len(crypt.WrappedDEK) == 0 {\n    return fmt.Errorf(\"box %s has no key envelope; skipping unlock\", boxID)\n}\n// then call the unlock path","typeGuard":"func hasKeyEnvelope(c *conf.BoxEncryption) bool {\n    return c != nil && len(c.WrappedDEK) > 0\n}","tryCatchPattern":null,"preventionTips":["Never hand-edit conf.json; let the kernel write BoxEncryption","Include conf.json (with envelope) in workspace backups and verify restores","Check notebook encryption state before calling unlock APIs"],"tags":["encryption","config","missing-key-material"],"backgroundTag":"missing-config-field","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}