siyuan-note/siyuan · error

unsupported encrypted envelope spec

Error message

unsupported encrypted envelope spec

What it means

EncryptionNonce validates the envelope's spec version byte immediately after the magic. This library only implements spec 1 (EncryptionSpec). A different byte means the envelope was written by a newer or incompatible format version and cannot be parsed here.

Solutions

  1. Upgrade SiYuan to the version that wrote the envelope (higher spec byte = newer format)
  2. Check the envelope's spec byte; if it is genuinely newer, do not attempt local decryption — sync/upgrade first
  3. If the data is foreign bytes that merely match 'SENC', locate the real envelope produced by Encrypt
Defensive patterns

Strategy: validation

Validate before calling

if len(data) >= 5 && data[4] != 0x01 {
    return fmt.Errorf("envelope spec %d not supported by this version; upgrade SiYuan", data[4])
}

Type guard

func isSupportedSpec(b []byte) bool {
    return len(b) >= 5 && string(b[:4]) == "SENC" && b[4] == 0x01
}

Try / catch

nonce, err := util.EncryptionNonce(ciphertext)
if err != nil {
    return fmt.Errorf("envelope spec unsupported (upgrade required?): %w", err)
}

Prevention

When it happens

Trigger: Calling EncryptionNonce on an envelope whose byte at offset 4 is not 0x01 — e.g. data written by a future SiYuan version with spec 2, or bytes that coincidentally start with 'SENC' but are not this format.

Common situations: Downgrading SiYuan after the envelope format was bumped, reading encrypted data synced from a newer client, or hand-crafted/foreign data beginning with 'SENC'.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/a05ded3c8101af52. Report an issue: GitHub.

Appendix: source

Thrown at kernel/util/kdf.go:110

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 派生用途隔离的子密钥。
// 同一 (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

View on GitHub (pinned to 9f775e8a12)