{"record":{"id":"0be4e884336d1656","repo":"siyuan-note/siyuan","slug":"hkdf-derive-failed","errorCode":null,"errorMessage":"hkdf derive failed: ","messagePattern":"hkdf derive failed: ","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"kernel/util/kdf.go","lineNumber":131,"sourceCode":"\t\treturn nil, errors.New(\"unsupported encrypted envelope algorithm\")\n\t}\n\tnonceLength := int(ciphertext[len(encryptionMagic)+2])\n\tif nonceLength == 0 || len(ciphertext) < encryptionEnvelopeHeaderSize+nonceLength {\n\t\treturn nil, errors.New(\"invalid encrypted envelope nonce length\")\n\t}\n\treturn append([]byte(nil), ciphertext[encryptionEnvelopeHeaderSize:encryptionEnvelopeHeaderSize+nonceLength]...), nil\n}\n\n// DeriveSubKey 用 HKDF-SHA256 从主 DEK 派生用途隔离的子密钥。\n// 同一 (dek, purpose) 多次调用结果一致；不同 purpose 派生出相互独立的子密钥，\n// 实现用途分离——.sy/assets/AV 各用独立子密钥，互不可替代，限制单点密钥泄漏的影响面。\nfunc DeriveSubKey(dek []byte, purpose string) []byte {\n\t// HKDF info 用 purpose 字节；salt 为 nil（DEK 本身已是高熵随机密钥，无需额外 salt）\n\tr := hkdf.New(sha256.New, dek, nil, []byte(purpose))\n\tout := make([]byte, 32) // AES-256\n\tif _, err := io.ReadFull(r, out); err != nil {\n\t\t// hkdf.Read 不应出错（除非 dek 为空）；防御性 panic 避免静默返回弱密钥\n\t\tpanic(\"hkdf derive failed: \" + err.Error())\n\t}\n\treturn out\n}\n\n// EncryptWithAAD 用 AES-256-GCM 加密并绑定 AAD（附加认证数据）。\n// AAD 不被加密，但参与 GCM 认证——解密时必须提供相同 AAD，否则认证失败。\n// 把用途/boxID/路径等元数据放入 AAD，可防止同 box 内密文被替换用途或路径（bind 到上下文）。\n// 返回格式与 Encrypt 一致，但 AAD 参与校验。\nfunc EncryptWithAAD(key, plaintext, aad []byte) ([]byte, error) {\n\treturn encryptGCM(key, plaintext, aad, \"EncryptWithAAD\")\n}\n\n// DecryptWithAAD 对应 EncryptWithAAD 的解密。格式无效、AAD 不匹配或密文被篡改时返回错误。\nfunc DecryptWithAAD(key, ciphertext, aad []byte) ([]byte, error) {\n\treturn decryptGCM(key, ciphertext, aad, \"DecryptWithAAD\")\n}\n\nfunc encryptGCM(key, plaintext, aad []byte, operation string) ([]byte, error) {","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/kdf.go#L113-L149","documentation":"DeriveSubKey runs HKDF-SHA256 over the master DEK and reads exactly 32 output bytes. The HKDF reader is deterministic and cannot fail for a non-empty key; if io.ReadFull returns an error (practically only when dek is empty/nil), the code panics rather than silently returning a weak or empty derived key.","triggerScenarios":"Calling DeriveSubKey with a nil or zero-length dek slice — e.g. a master key that failed to load/derive, an uninitialized DEK field in config, or an encryption flow running before EnableEncryptedNotebook populated the key.","commonSituations":"Calling AV/metadata encrypt-decrypt helpers (encryptAVData, decryptDataWithDEK, encryptBoxMetadata) before notebook encryption is enabled, a corrupted config where the DEK was lost, or a race where the DEK is read before initialization completes.","solutions":["Ensure the DEK is initialized (EnableEncryptedNotebook completed) before any derive/encrypt call","Check len(dek) == 32 at the call site and return a proper error instead of reaching the panic","If the DEK was lost because config was corrupted/restored, recover it from the wrapped-DEK backup envelope with the user password","Guard initialization ordering so decryption paths cannot run before key loading"],"exampleFix":"// before\nsub := util.DeriveSubKey(nil, \"av\") // panics\n\n// after\nif len(dek) != 32 {\n    return fmt.Errorf(\"DEK not initialized (len=%d)\", len(dek))\n}\nsub := util.DeriveSubKey(dek, \"av\")","handlingStrategy":"validation","validationCode":"if len(dek) != 32 {\n    return fmt.Errorf(\"DEK not ready (len=%d); enable notebook encryption first\", len(dek))\n}","typeGuard":"func dekReady(dek []byte) bool { return len(dek) == 32 }","tryCatchPattern":"func safeDeriveSubKey(dek []byte, purpose string) (out []byte, err error) {\n    defer func() {\n        if r := recover(); r != nil {\n            err = fmt.Errorf(\"derive failed: %v\", r)\n        }\n    }()\n    return util.DeriveSubKey(dek, purpose), nil\n}","preventionTips":["Gate all encrypt/decrypt helpers behind a confirmed-initialized DEK","Initialize keys once at startup and fail fast if loading fails","Never pass a nil/empty slice as a master key; check length == 32 at the boundary"],"tags":["hkdf","key-derivation","panic","uninitialized-key","invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}