{"record":{"id":"1225675c6c6405ea","repo":"siyuan-note/siyuan","slug":"encrypted-notebook-key-envelope-nonce-mismatch","errorCode":null,"errorMessage":"encrypted notebook key envelope nonce mismatch","messagePattern":"encrypted notebook key envelope nonce mismatch","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":1622,"sourceCode":"\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}\n\tif _, err := util.EncryptionNonce(enc.Metadata); err != nil {\n\t\treturn fmt.Errorf(\"invalid encrypted notebook metadata envelope: %w\", err)\n\t}\n\treturn nil\n}\n\n// mustEncryptionNonce 从刚刚成功生成的密文中提取 nonce。生成密文格式错误属于内部不变量被破坏，直接终止执行。\nfunc mustEncryptionNonce(ciphertext []byte) []byte {\n\tnonce, err := util.EncryptionNonce(ciphertext)\n\tif err != nil {","sourceCodeStart":1604,"sourceCodeEnd":1640,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/crypto.go#L1604-L1640","documentation":"Thrown by validateWrappedDEKEnvelope when the nonce embedded inside the WrappedDEK ciphertext envelope does not match the separately stored WrapNonce field. This is an integrity/consistency check: the WrapNonce is recorded at wrap time and must match the nonce in the ciphertext. A mismatch indicates the two fields were independently modified or come from different encryption operations.","triggerScenarios":"Fires during validateWrappedDEKEnvelope (reached from decryptWrappedDEK during unlock or ChangeMasterPassword). Concretely: util.EncryptionNonce(WrappedDEK) succeeds and returns a nonce, but bytes.Equal(nonce, enc.WrapNonce) is false.","commonSituations":"A sync conflict merged a WrappedDEK from one device with a WrapNonce from another. Manual editing of either field. A partial master-password migration updated WrappedDEK but not WrapNonce (or vice versa). Copy-paste errors when moving config between notebooks.","solutions":["Restore both WrappedDEK and WrapNonce from the same source (conf backup or per-notebook crypt backup) so they're consistent.","If a master-password migration is pending, restart SiYuan to let recoverMasterPasswordMigration re-write both fields atomically.","Force a full sync of the notebook's conf.json from a device where the pair is consistent."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Check nonce consistency before unlocking:\nnonce, err := util.EncryptionNonce(boxCrypt.WrappedDEK)\nif err == nil && !bytes.Equal(nonce, boxCrypt.WrapNonce) {\n    // nonce mismatch — conf is internally inconsistent, restore from backup\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always update WrappedDEK and WrapNonce together — they're produced by the same EncryptWithAAD call.","Resolve sync conflicts by taking the entire BoxCrypt struct from one source, not mixing fields.","Keep per-notebook backups so a consistent pair can be restored."],"tags":["encryption","nonce-mismatch","integrity","go"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}