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] = encryptionAlgorithmAES256GCMView on GitHub (pinned to 8641553a1f)
Solutions
- Derive the key with util.DeriveKey(password, salt, params) which returns KeyLength bytes (set KeyLength: 32)
- Use util.GenerateDEK() to create a proper 32-byte random data key
- 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
- Obtain keys only from util.DeriveKey (KeyLength: 32) or util.GenerateDEK()
- Never encrypt with a raw password, salt, or encoded string
- Decode hex/base64 key material and assert length before every encrypt call
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
- invalid encrypted envelope magic
- Argon2id KeyLength must be 32
- Argon2id Memory too low (minimum 64 MB)
- Argon2id Memory too high (maximum 256 MB)
- Argon2id Iterations too low (minimum 3)
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/51af4a06f71abb3b.
Report an issue: GitHub.