slackhq/nebula · error

salt must be set in argon2Parameters

Error message

salt must be set in argon2Parameters

What it means

deriveKey requires params.salt to be non-nil and at least 16 bytes. The public aes256DeriveKey auto-generates a random 32-byte salt when nil, so reaching this error means deriveKey was called directly (or via a path bypassing aes256DeriveKey) with no salt configured. A salt is required for Argon2id key derivation.

Source

Thrown at cert/crypto.go:135

	}

	// keySize of 32 bytes will result in AES-256 encryption
	key, err := deriveKey(passphrase, 32, params)
	if err != nil {
		return nil, err
	}

	return key, nil
}

// Derives a key from a passphrase using Argon2id
func deriveKey(passphrase []byte, keySize uint32, params *Argon2Parameters) ([]byte, error) {
	if params.version != argon2.Version {
		return nil, fmt.Errorf("incompatible Argon2 version: %d", params.version)
	}

	if params.salt == nil {
		return nil, fmt.Errorf("salt must be set in argon2Parameters")
	} else if len(params.salt) < 16 {
		return nil, fmt.Errorf("salt must be at least 128  bits")
	}

	key := argon2.IDKey(passphrase, params.salt, params.Iterations, params.Memory, params.Parallelism, keySize)

	return key, nil
}

// Prepends nonce to ciphertext
func joinNonceCiphertext(nonce []byte, ciphertext []byte) []byte {
	return append(nonce, ciphertext...)
}

// Splits nonce from ciphertext
func splitNonceCiphertext(blob []byte, nonceSize int) ([]byte, []byte, error) {
	if len(blob) <= nonceSize {
		return nil, nil, fmt.Errorf("invalid ciphertext blob - blob shorter than nonce length")

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Set a random >=16-byte salt: params.salt = make([]byte, 32); rand.Read(params.salt).
  2. Prefer the public flow (NewArgon2Parameters + aes256Encrypt/aes256Decrypt) which generates the salt automatically.
  3. If decrypting, ensure the salt stored in the metadata blob was not stripped or zeroed.

Example fix

// before
params := cert.NewArgon2Parameters(9*1024, 1, 4)
params.Salt = nil // derive fails downstream in custom KDF call
// after
salt := make([]byte, 32)
io.ReadFull(rand.Reader, salt)
params.Salt = salt
Defensive patterns

Strategy: validation

Validate before calling

params := cert.NewArgon2Parameters(9*1024, 1, 4)
// for decrypt flows, ensure salt from metadata is >= 16 bytes before use
if len(storedSalt) > 0 && len(storedSalt) < 16 {
    return fmt.Errorf("stored salt must be at least 16 bytes")
}

Type guard

func validSalt(salt []byte) bool {
    return len(salt) >= 16
}

Try / catch

if err != nil && strings.Contains(err.Error(), "salt must be set") {
    return fmt.Errorf("Argon2 parameters missing salt; use NewArgon2Parameters + the public encrypt/decrypt API")
}

Prevention

When it happens

Trigger: Calling deriveKey (unexported) or constructing Argon2Parameters and invoking a KDF path that skips aes256DeriveKey's nil-salt random generation, e.g. custom code or a salt that is a non-nil empty slice would hit the <16 bytes branch instead.

Common situations: Forks/custom crypto code that call deriveKey directly; tests building Argon2Parameters manually; deserializing parameters from data with a zero-length salt field.

Related errors


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/ae547295ae2d9e33. Report an issue: GitHub.