siyuan-note/siyuan · error
invalid encrypted envelope magic
Error message
invalid encrypted envelope magic
What it means
Raised by EncryptionNonce when the ciphertext does not begin with the 4-byte magic 'SENC' (checked by hasEncryptionMagic). EncryptionNonce extracts the nonce from a SiYuan AES-GCM envelope; input that is not an SESC envelope (plain JSON, random bytes, a truncated blob) is rejected at the first guard.
Source
Thrown at kernel/util/kdf.go:104
func DeriveKey(password string, salt []byte, p Argon2Params) []byte {
return argon2.IDKey([]byte(password), salt, p.Iterations, p.Memory, p.Parallelism, p.KeyLength)
}
// Encrypt 用 AES-256-GCM 加密。每次调用生成随机 nonce,因此同一明文多次加密结果不同。
// 返回格式:magic(4B) || spec(1B) || algorithm(1B) || nonceLength(1B) || nonce || ciphertext || GCM tag(16B)。
func Encrypt(key, plaintext []byte) ([]byte, error) {
return encryptGCM(key, plaintext, nil, "Encrypt")
}
// Decrypt 对应 Encrypt 的解密。密钥错误、格式无效或密文被篡改时返回错误。
func Decrypt(key, ciphertext []byte) ([]byte, error) {
return decryptGCM(key, ciphertext, nil, "Decrypt")
}
// EncryptionNonce 从 AES-GCM 密文信封中提取 nonce。
func EncryptionNonce(ciphertext []byte) ([]byte, error) {
if !hasEncryptionMagic(ciphertext) {
return nil, errors.New("invalid encrypted envelope magic")
}
if len(ciphertext) < encryptionEnvelopeHeaderSize {
return nil, errors.New("encrypted envelope too short")
}
if ciphertext[len(encryptionMagic)] != EncryptionSpec {
return nil, errors.New("unsupported encrypted envelope spec")
}
if ciphertext[len(encryptionMagic)+1] != encryptionAlgorithmAES256GCM {
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 派生用途隔离的子密钥。View on GitHub (pinned to 251596fc0d)
Solutions
- Pre-check with util.IsCiphertext(blob) (which is hasEncryptionMagic) before calling EncryptionNonce, and skip non-ciphertext blobs.
- Ensure you are reading from an encrypted notebook whose metadata was written by Encrypt/EncryptWithAAD.
- If the conf is corrupted, restore from a known-good backup rather than forcing decryption.
Example fix
// before
nonce, err := util.EncryptionNonce(meta)
if err != nil { return err }
// after: guard with the magic check first
if !util.IsCiphertext(meta) {
return fmt.Errorf("metadata is not an encrypted envelope")
}
nonce, err := util.EncryptionNonce(meta)
if err != nil { return err } Defensive patterns
Strategy: type-guard
Type guard
// util.IsCiphertext already exposes this check.
if !util.IsCiphertext(blob) {
// not an SESC envelope: skip nonce extraction / treat as plain
return
} Prevention
- Always gate EncryptionNonce with util.IsCiphertext.
- Do not call EncryptionNonce on blobs from unencrypted notebooks.
- Use mustEncryptionNonce only on ciphertext you just produced with Encrypt.
When it happens
Trigger: EncryptionNonce is called on KEKVerifier / WrappedDEK / Metadata blobs during notebook crypto operations (crypto_lifecycle.go, crypto.go). If the stored blob is not an SESC envelope (e.g. an unencrypted legacy value, corrupted bytes, or a different format), the magic check fails.
Common situations: Reading a notebook that was never encrypted (plain JSON where a ciphertext was expected); partial corruption of the conf; mixing encrypted and unencrypted notebooks; a path migration that placed a non-ciphertext object where ciphertext is expected (the IsCiphertext guard exists exactly to pre-filter these).
Related errors
- Argon2id KeyLength must be 32
- Argon2id Memory too low (minimum 64 MB)
- Argon2id Memory too high (maximum 256 MB)
- Argon2id Iterations too low (minimum 3)
- Argon2id Iterations too high (maximum 10)
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/6fe9c7495c5bda53.
Report an issue: GitHub.