siyuan-note/siyuan · error

requires a 32-byte (AES-256) key

Error message

 requires a 32-byte (AES-256) key

What it means

encryptGCM (backing Encrypt and EncryptWithAAD) accepts only exactly 32-byte keys because it performs AES-256-GCM. A shorter or longer key is rejected up front with a message prefixed by the operation name (e.g. "Encrypt requires a 32-byte (AES-256) key") instead of letting aes.NewCipher produce a vaguer failure or silently weakening the cipher.

Source

Thrown at kernel/util/kdf.go:151

	return out
}

// EncryptWithAAD 用 AES-256-GCM 加密并绑定 AAD(附加认证数据)。
// AAD 不被加密,但参与 GCM 认证——解密时必须提供相同 AAD,否则认证失败。
// 把用途/boxID/路径等元数据放入 AAD,可防止同 box 内密文被替换用途或路径(bind 到上下文)。
// 返回格式与 Encrypt 一致,但 AAD 参与校验。
func EncryptWithAAD(key, plaintext, aad []byte) ([]byte, error) {
	return encryptGCM(key, plaintext, aad, "EncryptWithAAD")
}

// DecryptWithAAD 对应 EncryptWithAAD 的解密。格式无效、AAD 不匹配或密文被篡改时返回错误。
func DecryptWithAAD(key, ciphertext, aad []byte) ([]byte, error) {
	return decryptGCM(key, ciphertext, aad, "DecryptWithAAD")
}

func encryptGCM(key, plaintext, aad []byte, operation string) ([]byte, error) {
	if len(key) != 32 {
		return nil, errors.New(operation + " requires a 32-byte (AES-256) key")
	}
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, err
	}
	nonceSize := gcm.NonceSize()
	nonce := make([]byte, nonceSize)
	if _, err = rand.Read(nonce); err != nil {
		return nil, err
	}
	envelope := make([]byte, encryptionEnvelopeHeaderSize, encryptionEnvelopeHeaderSize+nonceSize+len(plaintext)+gcm.Overhead())
	copy(envelope, encryptionMagic[:])
	envelope[len(encryptionMagic)] = EncryptionSpec
	envelope[len(encryptionMagic)+1] = encryptionAlgorithmAES256GCM

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Derive the key with util.DeriveKey(password, salt, params) which returns KeyLength bytes (set KeyLength: 32)
  2. Use util.GenerateDEK() to create a proper 32-byte random data key
  3. If the key is encoded, decode it first (hex.DecodeString/base64.StdEncoding.DecodeString) and confirm len == 32 before encrypting

Example fix

// before
key := []byte(password)
ciphertext, err := util.Encrypt(key, plaintext) // wrong length
// after
developerKey := util.DeriveKey(password, salt, util.Argon2Params{Memory: 64 * 1024, Iterations: 3, Parallelism: 4, KeyLength: 32})
ciphertext, err := util.Encrypt(developerKey, plaintext)
Defensive patterns

Strategy: validation

Validate before calling

if len(key) != 32 { return fmt.Errorf("encryption key must be 32 bytes, got %d", len(key)) }

Type guard

func isAES256Key(key []byte) bool { return len(key) == 32 }

Try / catch

ciphertext, err := util.Encrypt(key, plaintext)
if err != nil && strings.Contains(err.Error(), "32-byte") {
    return nil, fmt.Errorf("bad key material: %w", err)
}

Prevention

When it happens

Trigger: Calling util.Encrypt or util.EncryptWithAAD with a key that is not 32 bytes — e.g. a raw password string, a 16-byte AES-128 key, a hex/base64 string passed instead of decoded bytes, or a sub-key derived with a non-32 output length.

Common situations: Passing the user's password directly instead of running it through DeriveKey; decoding a base64 DEK into a string rather than []byte; truncating keys with [:16] to 'save space'; using salt (16 bytes) as the key.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/51af4a06f71abb3b. Report an issue: GitHub.