ory/hydra · critical

secret for signing HMAC-SHA512/256 is expected to be 32 byte

Error message

secret for signing HMAC-SHA512/256 is expected to be 32 byte long, got %d byte

What it means

HMACStrategy.Generate refuses to sign tokens when the configured global secret is shorter than the 32-byte minimum required for HMAC-SHA512/256 (the signing key is a 32-byte array). This is a server configuration problem: the secret in the environment/config is too short (or empty).

Source

Thrown at fosite/token/hmac/hmacsha.go:57

	minimumEntropy      = 32
	minimumSecretLength = 32
)

var b64 = base64.URLEncoding.WithPadding(base64.NoPadding)

// Generate generates a token and a matching signature or returns an error.
// This method implements rfc6819 Section 5.1.4.2.2: Use High Entropy for Secrets.
func (c *HMACStrategy) Generate(ctx context.Context) (string, string, error) {
	c.Lock()
	defer c.Unlock()

	globalSecret, err := c.Config.GetGlobalSecret(ctx)
	if err != nil {
		return "", "", err
	}

	if len(globalSecret) < minimumSecretLength {
		return "", "", errors.Errorf("secret for signing HMAC-SHA512/256 is expected to be 32 byte long, got %d byte", len(globalSecret))
	}

	var signingKey [32]byte
	copy(signingKey[:], globalSecret)

	entropy := c.Config.GetTokenEntropy(ctx)
	if entropy < minimumEntropy {
		entropy = minimumEntropy
	}

	// When creating tokens not intended for usage by human users (e.g.,
	// client secrets or token handles), the authorization server should
	// include a reasonable level of entropy in order to mitigate the risk
	// of guessing attacks. The token value should be >=128 bits long and
	// constructed from a cryptographically strong random or pseudo-random
	// number sequence (see [RFC4086] for best current practice) generated
	// by the authorization server.
	tokenKey, err := RandomBytes(entropy)

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Set a global secret of at least 32 bytes (e.g. generate with: export OAUTH2_SHARED_SECRET=$(openssl rand -hex 32) or head -c 32 /dev/urandom | base64 depending on encoding)
  2. Restart/redeploy the service so the new secret is loaded
  3. Check for encoding pitfalls: if the value is hex/base64 encoded, decode before measuring or supply 32 raw bytes
  4. Ensure all replicas share the same long secret, otherwise token validation will break next

Example fix

// before
export OAUTH2_SHARED_SECRET="supersecret"
// after
export OAUTH2_SHARED_SECRET="$(openssl rand -hex 32)"  # 64 hex chars = 32 bytes
Defensive patterns

Strategy: validation

Validate before calling

secret, _ := cfg.GetGlobalSecret(context.Background())
if len(secret) < 32 {
    log.Fatalf("global secret too short: %d bytes (need >= 32)", len(secret))
}

Try / catch

_, err := tokens.GenerateAccessToken(ctx, req)
if err != nil && strings.Contains(err.Error(), "expected to be 32 byte long") {
    log.Fatalf("misconfigured global secret: %v", err)
}

Prevention

When it happens

Trigger: Generating any HMAC-based artifact (access token, refresh token, authorize code, device code) via GenerateAccessToken/GenerateRefreshToken/GenerateAuthorizeCode/GenerateDeviceCode when GetGlobalSecret(ctx) returns fewer than 32 bytes.

Common situations: Dev/test secrets like 'some-secret' left in production config; missing or empty OAUTH2_SHARED_SECRET / COOKIE_SECRET env var; docker-compose examples with short secrets not replaced during deployment.

Related errors


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/b87e9da3209994d5. Report an issue: GitHub.