{"record":{"id":"dcfa6b449fc2c6d2","repo":"siyuan-note/siyuan","slug":"unsupported-encrypted-notebook-key-envelope","errorCode":null,"errorMessage":"unsupported encrypted notebook key envelope","messagePattern":"unsupported encrypted notebook key envelope","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":1612,"sourceCode":"\t\tWrapNonce:  mustEncryptionNonce(wrapped),\n\t\tCreatedAt:  time.Now().UnixMilli(),\n\t}, dek, nil\n}\n\nfunc wrappedDEKAAD(boxID string) []byte {\n\treturn []byte(\"siyuan:wrapped-dek:\" + boxID)\n}\n\nfunc decryptWrappedDEK(boxID string, enc *conf.BoxEncryption, kek []byte) ([]byte, error) {\n\tif err := validateWrappedDEKEnvelope(enc); err != nil {\n\t\treturn nil, err\n\t}\n\treturn util.DecryptWithAAD(kek, enc.WrappedDEK, wrappedDEKAAD(boxID))\n}\n\nfunc validateWrappedDEKEnvelope(enc *conf.BoxEncryption) error {\n\tif enc == nil || enc.Spec != boxEncryptionSpec {\n\t\treturn errors.New(\"unsupported encrypted notebook key envelope\")\n\t}\n\tif enc.CreatedAt <= 0 {\n\t\treturn errors.New(\"encrypted notebook key envelope creation time is missing\")\n\t}\n\tnonce, err := util.EncryptionNonce(enc.WrappedDEK)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid encrypted notebook key envelope: %w\", err)\n\t}\n\tif !bytes.Equal(nonce, enc.WrapNonce) {\n\t\treturn errors.New(\"encrypted notebook key envelope nonce mismatch\")\n\t}\n\treturn nil\n}\n\nfunc validateBoxEncryption(enc *conf.BoxEncryption) error {\n\tif err := validateWrappedDEKEnvelope(enc); err != nil {\n\t\treturn err\n\t}","sourceCodeStart":1594,"sourceCodeEnd":1630,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/crypto.go#L1594-L1630","documentation":"Thrown by validateWrappedDEKEnvelope when the BoxEncryption struct is nil or its Spec field does not equal boxEncryptionSpec (currently 1). This is the first structural validation before any decryption is attempted — it rejects envelopes from an incompatible or unknown encryption spec before touching crypto primitives.","triggerScenarios":"Called indirectly via decryptWrappedDEK, which is reached from decryptBoxCrypt → GetBoxEncryption during unlock, or from ChangeMasterPassword Phase 0. Fires when conf.json or the per-notebook backup contains a BoxCrypt with Spec=0 (unset), Spec=2+ (future version), or when BoxCrypt is entirely nil.","commonSituations":"Downgrading SiYuan to an older version that doesn't understand a newer spec. Manually editing conf.json and losing the Spec field. A third-party tool or sync conflict produced a BoxCrypt JSON object without the Spec field. Importing a notebook from a different or future SiYuan version.","solutions":["Update SiYuan to the version that created the notebook — a newer spec may be in use.","Restore conf.json from a DataDir backup that has a valid Spec=1 BoxCrypt.","Restore the per-notebook crypt backup if it has the correct Spec.","If the notebook was created on an incompatible version, export the data from the original version and re-import on this version."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before unlocking, verify the BoxEncryption envelope spec:\nboxCrypt, err := model.GetBoxEncryption(boxID)\nif err != nil {\n    return\n}\nif boxCrypt.Spec != 1 { // boxEncryptionSpec\n    // incompatible spec — update SiYuan or restore a compatible conf\n    return\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep SiYuan updated to handle the latest encryption spec.","Never downgrade SiYuan below the version that created an encrypted notebook.","Avoid manually constructing BoxEncryption JSON — always use the API."],"tags":["encryption","config-validation","spec-mismatch","go"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}