JuliusBrussee/caveman · error

aes cipher

Error message

aes cipher: %w

What it means

After loading the local key, secretbox.Encrypt calls aes.NewCipher(keyBytes) to build an AES block cipher. Go's aes package returns an error only when the key length is not 16, 24, or 32 bytes (AES-128/192/256). This error therefore means the key material produced by loadKey() has an invalid length.

Solutions

  1. Print len(keyBytes) at loadKey (or decode manually in a scratch program) and confirm it is exactly 16, 24, or 32 bytes.
  2. Regenerate the key correctly, e.g. openssl rand -base64 32, and set the env var with no quoting/newline issues.
  3. Ensure the decoding scheme in loadKey (hex vs base64) matches how the key was generated and stored.
  4. Trim whitespace/newlines before decoding, or fix the stored value rather than trimming at runtime.

Example fix

// before: raw passphrase used as key -> aes cipher: crypto/aes: invalid key size 11
key := os.Getenv("CAVE_SECRETBOX_KEY")

// after: exactly 32 random bytes, base64-decoded
keyB64 := strings.TrimSpace(os.Getenv("CAVE_SECRETBOX_KEY"))
key, err := base64.StdEncoding.DecodeString(keyB64) // len == 32
if err != nil || (len(key) != 16 && len(key) != 24 && len(key) != 32) {
    log.Fatal("CAVE_SECRETBOX_KEY must decode to 16/24/32 bytes")
}
Defensive patterns

Strategy: validation

Validate before calling

key, err := base64.StdEncoding.DecodeString(strings.TrimSpace(os.Getenv("CAVE_SECRETBOX_KEY")))
if err != nil || (len(key) != 16 && len(key) != 24 && len(key) != 32) {
    return fmt.Errorf("CAVE_SECRETBOX_KEY must decode to 16/24/32 bytes, got %d", len(key))
}

Try / catch

cipherText, err := secretbox.Encrypt(pt)
if err != nil {
    if strings.Contains(err.Error(), "aes cipher") {
        return fmt.Errorf("misconfigured secretbox key: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling secretbox.Encrypt with useKMS() false (non-production) when CAVE_SECRETBOX_KEY (or whatever loadKey reads) decodes to a byte slice whose length is not 16/24/32 — e.g. a hex/base64 string truncated, double-encoded, or with stray whitespace/newline included in the decoded bytes.

Common situations: Pasting a key with a trailing newline or quotes into the env var; using a raw passphrase string of arbitrary length instead of a decoded 32-byte key; key file truncated by copy/paste or editor; switching between hex and base64 encodings without adjusting.

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 JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/172b346b29723217. Report an issue: GitHub.

Appendix: source

Thrown at shared/platform/secretbox/secretbox.go:72

	if useKMS() {
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		wrapped, err := kms.Encrypt(ctx, plaintext)
		if err != nil {
			return nil, fmt.Errorf("secretbox: KMS encrypt: %w", err)
		}
		return wrapped, nil
	}
	if runtimeenv.IsProduction() {
		return nil, fmt.Errorf("secretbox: production requires CAVE_KMS_PROVIDER=scaleway")
	}
	keyBytes, err := loadKey()
	if err != nil {
		return nil, err
	}
	block, err := aes.NewCipher(keyBytes)
	if err != nil {
		return nil, fmt.Errorf("aes cipher: %w", err)
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, fmt.Errorf("aes-gcm: %w", err)
	}
	nonce := make([]byte, gcm.NonceSize())
	if _, err := rand.Read(nonce); err != nil {
		return nil, fmt.Errorf("nonce entropy: %w", err)
	}
	// Seal appends the ciphertext+tag to nonce, so the returned slice is the
	// full nonce||ciphertext envelope.
	return gcm.Seal(nonce, nonce, plaintext, nil), nil
}

// EncryptPayloadKey wraps an artifact data-encryption key. Production uses the
// dedicated payload KEK; local development retains the same AES-GCM envelope as
// other local secrets.
func EncryptPayloadKey(plaintext []byte) ([]byte, error) {

View on GitHub (pinned to 3ee70a1026)