semaphoreui/semaphore · critical

access_key_encryption has invalid decoded length

Error message

access_key_encryption has invalid decoded length %d bytes; AES requires 16, 24, or 32 bytes (use `openssl rand -base64 32` to generate a valid key)

What it means

The access_key_encryption value decoded from base64 successfully, but its byte length is not 16, 24, or 32 — the only key sizes AES accepts (AES-128/192/256). The error reports the actual decoded length and suggests generating a key with `openssl rand -base64 32`. It fails startup via panic in validateConfig.

Solutions

  1. Generate a fresh valid key with `openssl rand -base64 32` and replace the configured value.
  2. Pad/truncate key material to exactly 16, 24, or 32 decoded bytes — verify with `base64 -d <value> | wc -c`.
  3. Do not double-encode: if the value already looks base64, decode it, check its length, and use the correct single encoding.
  4. If old data was encrypted with a 16/24/32-byte key, recover that exact key; you cannot change lengths without re-encrypting existing access keys.

Example fix

// before: 20-byte decoded key
export SEMAPHORE_ACCESS_KEY_ENCRYPTION=$(openssl rand -base64 15)  # decodes to 15 bytes
// after
export SEMAPHORE_ACCESS_KEY_ENCRYPTION=$(openssl rand -base64 32)  # decodes to 32 bytes
Defensive patterns

Strategy: validation

Validate before calling

raw, err := base64.StdEncoding.DecodeString(value)
if err != nil {
    log.Fatal("not base64")
}
switch len(raw) {
case 16, 24, 32:
    // ok
default:
    log.Fatalf("decoded %d bytes; need 16/24/32", len(raw))
}

Try / catch

defer func() {
    if r := recover(); r != nil {
        log.Fatalf("invalid AES key length: %v", r)
    }
}()

Prevention

When it happens

Trigger: validateAccessKeyEncryption() decodes a non-empty access_key_encryption/option_encryption value and the switch on len(encryption) hits the default case (decoded length not in {16,24,32}).

Common situations: User generated `openssl rand -base64 16` thinking 16 chars, but truncating a 32-byte base64 string yields e.g. 20 decoded bytes; base64 of a short password (e.g. 8-10 bytes); re-encoding an already-base64 string (double encoding changes length); hand-typed key of arbitrary length.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07). Data as JSON: /api/errors/29dc0eb882d38827. Report an issue: GitHub.

Appendix: source

Thrown at util/config.go:1742

	return &enc, nil
}

func validateAccessKeyEncryption(key string) error {
	if key == "" {
		return nil
	}

	encryption, err := base64.StdEncoding.DecodeString(key)
	if err != nil {
		return fmt.Errorf("access_key_encryption must be a valid base64 string: %w", err)
	}

	switch len(encryption) {
	case 16, 24, 32:
		return nil
	default:
		return fmt.Errorf(
			"access_key_encryption has invalid decoded length %d bytes; AES requires 16, 24, or 32 bytes (use `openssl rand -base64 32` to generate a valid key)",
			len(encryption),
		)
	}
}

func validateConfig() {
	err := validate(Config)
	if err != nil {
		panic(err)
	}

	if err := validateAccessKeyEncryption(Config.AccessKeyEncryption); err != nil {
		panic(err)
	}
	if err := validateAccessKeyEncryption(Config.OptionEncryption); err != nil {
		panic(err)
	}

View on GitHub (pinned to 1774ccb71a)