siyuan-note/siyuan · error

encrypted notebook key envelope nonce mismatch

Error message

encrypted notebook key envelope nonce mismatch

What it means

Thrown by validateWrappedDEKEnvelope when the nonce embedded inside the WrappedDEK ciphertext envelope does not match the separately stored WrapNonce field. This is an integrity/consistency check: the WrapNonce is recorded at wrap time and must match the nonce in the ciphertext. A mismatch indicates the two fields were independently modified or come from different encryption operations.

Source

Thrown at kernel/model/crypto.go:1622

	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)
	}
	return nil
}

// mustEncryptionNonce 从刚刚成功生成的密文中提取 nonce。生成密文格式错误属于内部不变量被破坏,直接终止执行。
func mustEncryptionNonce(ciphertext []byte) []byte {
	nonce, err := util.EncryptionNonce(ciphertext)
	if err != nil {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Restore both WrappedDEK and WrapNonce from the same source (conf backup or per-notebook crypt backup) so they're consistent.
  2. If a master-password migration is pending, restart SiYuan to let recoverMasterPasswordMigration re-write both fields atomically.
  3. Force a full sync of the notebook's conf.json from a device where the pair is consistent.
Defensive patterns

Strategy: validation

Validate before calling

// Check nonce consistency before unlocking:
nonce, err := util.EncryptionNonce(boxCrypt.WrappedDEK)
if err == nil && !bytes.Equal(nonce, boxCrypt.WrapNonce) {
    // nonce mismatch — conf is internally inconsistent, restore from backup
}

Prevention

When it happens

Trigger: Fires during validateWrappedDEKEnvelope (reached from decryptWrappedDEK during unlock or ChangeMasterPassword). Concretely: util.EncryptionNonce(WrappedDEK) succeeds and returns a nonce, but bytes.Equal(nonce, enc.WrapNonce) is false.

Common situations: A sync conflict merged a WrappedDEK from one device with a WrapNonce from another. Manual editing of either field. A partial master-password migration updated WrappedDEK but not WrapNonce (or vice versa). Copy-paste errors when moving config between notebooks.

Related errors


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