siyuan-note/siyuan · error

invalid encrypted envelope nonce length

Error message

invalid encrypted envelope nonce length

What it means

EncryptionNonce reads the nonce length byte (offset 6) and checks it is non-zero and that the envelope actually contains that many bytes after the 7-byte header. A zero length or a length exceeding the remaining buffer means the envelope is malformed or truncated.

Solutions

  1. Verify the full envelope was written/read — compare file size against the expected envelope length
  2. Restore the encrypted blob from backup or re-sync it; a truncated GCM envelope is unrecoverable in place
  3. Check the write path for missing error handling on file writes that could leave partial envelopes
  4. If only extracting the nonce for diagnostics, treat this envelope as corrupt and skip it

Example fix

// before
nonce, err := util.EncryptionNonce(envelope[:len(envelope)-8]) // chopped tail

// after
nonce, err := util.EncryptionNonce(envelope)
if err != nil { return fmt.Errorf("envelope corrupt: %w", err) }
Defensive patterns

Strategy: validation

Validate before calling

if len(data) >= 7 {
    nl := int(data[6])
    if nl == 0 || len(data) < 7+nl {
        return errors.New("envelope nonce length invalid or body truncated")
    }
}

Type guard

func envelopeComplete(b []byte) bool {
    if len(b) < 7 { return false }
    nl := int(b[6])
    return nl > 0 && len(b) >= 7+nl
}

Try / catch

nonce, err := util.EncryptionNonce(ciphertext)
if err != nil {
    return fmt.Errorf("envelope corrupt/truncated, restore from backup: %w", err)
}

Prevention

When it happens

Trigger: Calling EncryptionNonce on an envelope where byte 6 is 0x00 (corrupt header) or where len(ciphertext) < 7 + nonceLength (body truncated), e.g. a partially written or partially synced encrypted file.

Common situations: Interrupted writes (crash/power loss mid-write), incomplete sync of encrypted blobs, manual truncation of ciphertext, or corruption flipping the length byte.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at kernel/util/kdf.go:117

}

// 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
	if _, err := io.ReadFull(r, out); err != nil {
		// hkdf.Read 不应出错(除非 dek 为空);防御性 panic 避免静默返回弱密钥
		panic("hkdf derive failed: " + err.Error())
	}
	return out
}

View on GitHub (pinned to 9f775e8a12)