siyuan-note/siyuan · critical
hkdf derive failed:
Error message
hkdf derive failed:
What it means
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.
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
Example fix
// before
sub := util.DeriveSubKey(nil, "av") // panics
// after
if len(dek) != 32 {
return fmt.Errorf("DEK not initialized (len=%d)", len(dek))
}
sub := util.DeriveSubKey(dek, "av") Defensive patterns
Strategy: validation
Validate before calling
if len(dek) != 32 {
return fmt.Errorf("DEK not ready (len=%d); enable notebook encryption first", len(dek))
} Type guard
func dekReady(dek []byte) bool { return len(dek) == 32 } Try / catch
func safeDeriveSubKey(dek []byte, purpose string) (out []byte, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("derive failed: %v", r)
}
}()
return util.DeriveSubKey(dek, purpose), nil
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- attribute view rich text style entity sentinel was not…
- attribute view [ ] template plan not found
- block rowid not found after insert
- clone attribute view
- Conf.Language(310)
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/0be4e884336d1656.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/kdf.go:131
return nil, errors.New("unsupported encrypted envelope algorithm")
}
nonceLength := int(ciphertext[len(encryptionMagic)+2])
if nonceLength == 0 || len(ciphertext) < encryptionEnvelopeHeaderSize+nonceLength {
return nil, errors.New("invalid encrypted envelope nonce length")
}
return append([]byte(nil), ciphertext[encryptionEnvelopeHeaderSize:encryptionEnvelopeHeaderSize+nonceLength]...), nil
}
// DeriveSubKey 用 HKDF-SHA256 从主 DEK 派生用途隔离的子密钥。
// 同一 (dek, purpose) 多次调用结果一致;不同 purpose 派生出相互独立的子密钥,
// 实现用途分离——.sy/assets/AV 各用独立子密钥,互不可替代,限制单点密钥泄漏的影响面。
func DeriveSubKey(dek []byte, purpose string) []byte {
// HKDF info 用 purpose 字节;salt 为 nil(DEK 本身已是高熵随机密钥,无需额外 salt)
r := hkdf.New(sha256.New, dek, nil, []byte(purpose))
out := make([]byte, 32) // AES-256
if _, err := io.ReadFull(r, out); err != nil {
// hkdf.Read 不应出错(除非 dek 为空);防御性 panic 避免静默返回弱密钥
panic("hkdf derive failed: " + err.Error())
}
return out
}
// EncryptWithAAD 用 AES-256-GCM 加密并绑定 AAD(附加认证数据)。
// AAD 不被加密,但参与 GCM 认证——解密时必须提供相同 AAD,否则认证失败。
// 把用途/boxID/路径等元数据放入 AAD,可防止同 box 内密文被替换用途或路径(bind 到上下文)。
// 返回格式与 Encrypt 一致,但 AAD 参与校验。
func EncryptWithAAD(key, plaintext, aad []byte) ([]byte, error) {
return encryptGCM(key, plaintext, aad, "EncryptWithAAD")
}
// DecryptWithAAD 对应 EncryptWithAAD 的解密。格式无效、AAD 不匹配或密文被篡改时返回错误。
func DecryptWithAAD(key, ciphertext, aad []byte) ([]byte, error) {
return decryptGCM(key, ciphertext, aad, "DecryptWithAAD")
}
func encryptGCM(key, plaintext, aad []byte, operation string) ([]byte, error) {View on GitHub (pinned to 9f775e8a12)