grafana/k6 · error

failed to decode symmetric key: %w

Error message

failed to decode symmetric key: %w

What it means

Thrown by k6 WebCrypto when the k field of a symmetric JWK is not valid base64url encoding. After validation succeeds, extractSymmetricJWK (internal/js/modules/k6/webcrypto/jwk.go:60-63) runs base64URLDecode on k; standard base64 characters ('+', '/'), bad padding, or non-alphabet characters fail and the underlying decoding error is wrapped with %w.

Source

Thrown at internal/js/modules/k6/webcrypto/jwk.go:62

	}

	return nil
}

// extractSymmetricJWK extracts the symmetric key from a given JWK key (JSON data).
func extractSymmetricJWK(jsonKeyData []byte) ([]byte, error) {
	sk := symmetricJWK{}
	if err := json.Unmarshal(jsonKeyData, &sk); err != nil {
		return nil, fmt.Errorf("failed to parse symmetric JWK: %w", err)
	}

	if err := sk.validate(); err != nil {
		return nil, fmt.Errorf("invalid symmetric JWK: %w", err)
	}

	skBytes, err := base64URLDecode(sk.K)
	if err != nil {
		return nil, fmt.Errorf("failed to decode symmetric key: %w", err)
	}

	return skBytes, nil
}

// exportSymmetricJWK exports a symmetric key as a map of JWK key parameters.
func exportSymmetricJWK(key *CryptoKey) (*JsonWebKey, error) {
	rawKey, ok := key.handle.([]byte)
	if !ok {
		return nil, errors.New("key's handle isn't a byte slice")
	}

	// wrap result into the object that is expected to be returned
	exported := &JsonWebKey{}

	exported.Set("k", base64URLEncode(rawKey))
	exported.Set("kty", JWKOctKeyType)
	exported.Set("ext", key.Extractable)

View on GitHub (pinned to 93accf6570)

Solutions

  1. Convert standard base64 to base64url: replace '+' with '-', '/' with '_', and strip '=' padding (or use k6's encoding/base64: Base64.encodings.Base64Url without padding).
  2. Ensure no whitespace or newlines inside k.
  3. If the secret is hex, decode hex to bytes and re-encode as base64url before putting it in the JWK.

Example fix

// before
const k = 'a+b/cd=='; // standard base64 with padding
await crypto.subtle.importKey('jwk', { kty: 'oct', k }, alg, false, ['encrypt']);

// after
import encoding from 'k6/encoding';
const k = encoding.b64encode(encoding.b64decode('a+b/cd==', 'std'), 'url').replace(/=+$/, '');
await crypto.subtle.importKey('jwk', { kty: 'oct', k }, alg, false, ['encrypt']);
Defensive patterns

Strategy: validation

Validate before calling

function toBase64url(b64) {
  return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
const k = toBase64url(rawSecretB64); // then { kty: 'oct', k }

Type guard

const isBase64Url = (s) => typeof s === 'string' && /^[A-Za-z0-9_-]+$/.test(s);

Prevention

When it happens

Trigger: k contains standard-base64 ('+'/'/' characters) instead of base64url ('-'/'_'); k has trailing '=' padding that violates the strict decoder; k includes whitespace/newlines from being wrapped in a certificate-style layout; k is hex-encoded rather than base64.

Common situations: Secrets copied from JWT headers or vaults that emit standard base64; line-wrapped base64 pasted from terminals; secrets stored as hex by another system.

Understand the failure class

Related errors


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/eb9ab90fca94d5d2. Report an issue: GitHub.