siyuan-note/siyuan · error

encrypted notebook key envelope creation time is missing

Error message

encrypted notebook key envelope creation time is missing

What it means

Thrown by validateWrappedDEKEnvelope when enc.CreatedAt <= 0. CreatedAt is a Unix-millisecond timestamp set at key-wrap time; it must be positive. A zero or negative value indicates the field was never set, was lost during serialization, or was corrupted.

Source

Thrown at kernel/model/crypto.go:1615

}

func wrappedDEKAAD(boxID string) []byte {
	return []byte("siyuan:wrapped-dek:" + boxID)
}

func decryptWrappedDEK(boxID string, enc *conf.BoxEncryption, kek []byte) ([]byte, error) {
	if err := validateWrappedDEKEnvelope(enc); err != nil {
		return nil, err
	}
	return util.DecryptWithAAD(kek, enc.WrappedDEK, wrappedDEKAAD(boxID))
}

func validateWrappedDEKEnvelope(enc *conf.BoxEncryption) error {
	if enc == nil || enc.Spec != boxEncryptionSpec {
		return errors.New("unsupported encrypted notebook key envelope")
	}
	if enc.CreatedAt <= 0 {
		return errors.New("encrypted notebook key envelope creation time is missing")
	}
	nonce, err := util.EncryptionNonce(enc.WrappedDEK)
	if err != nil {
		return fmt.Errorf("invalid encrypted notebook key envelope: %w", err)
	}
	if !bytes.Equal(nonce, enc.WrapNonce) {
		return errors.New("encrypted notebook key envelope nonce mismatch")
	}
	return nil
}

func validateBoxEncryption(enc *conf.BoxEncryption) error {
	if err := validateWrappedDEKEnvelope(enc); err != nil {
		return err
	}
	if _, err := util.EncryptionNonce(enc.Metadata); err != nil {
		return fmt.Errorf("invalid encrypted notebook metadata envelope: %w", err)
	}

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Restore conf.json from a backup that has a valid CreatedAt timestamp.
  2. Restore the per-notebook crypt backup if it has a valid CreatedAt.
  3. If you have access to the source code and are migrating notebooks programmatically, ensure CreatedAt is set to time.Now().UnixMilli() when constructing BoxEncryption.

Example fix

// before: BoxEncryption missing CreatedAt
boxConf.BoxCrypt = &conf.BoxEncryption{
    WrappedDEK: wrapped,
    WrapNonce:  nonce,
    Spec:       boxEncryptionSpec,
    // CreatedAt missing
}
// after
boxConf.BoxCrypt = &conf.BoxEncryption{
    WrappedDEK: wrapped,
    WrapNonce:  nonce,
    Spec:       boxEncryptionSpec,
    CreatedAt:  time.Now().UnixMilli(),
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate the envelope before attempting unlock:
boxCrypt, err := model.GetBoxEncryption(boxID)
if err != nil {
    return
}
if boxCrypt.CreatedAt <= 0 {
    // timestamp missing — restore conf from backup
    return
}

Prevention

When it happens

Trigger: Reached via decryptWrappedDEK during unlock or ChangeMasterPassword. Fires when the BoxEncryption struct's CreatedAt field is 0 or negative — e.g., the JSON field was omitted during manual editing, a sync conflict dropped it, or the struct was constructed without setting CreatedAt.

Common situations: Manual editing of conf.json removed the CreatedAt field. A sync-conflict resolution script or third-party merge tool dropped the field. A bug in a custom import/migration tool that constructs BoxEncryption without setting CreatedAt.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/943b7f35cd761f88. Report an issue: GitHub.