grafana/k6 · error

failed to decode first exponent: %w

Error message

failed to decode first exponent: %w

What it means

During RSA private-key JWK import, the CRT exponent dp (JWK field 'dp', exponent1) is decoded with base64.RawURLEncoding. This error indicates dp is not valid unpadded base64url: padding, '+'/'/' characters, whitespace, or bad length. Fields dq and qi have identical checks immediately after, so fixing encoding once usually resolves all three.

Source

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

	if jwk.D == "" {
		return pubKey, PublicCryptoKeyType, pubKey.N.BitLen(), nil
	}

	dBytes, err := base64URLDecode(jwk.D)
	if err != nil {
		return nil, UnknownCryptoKeyType, 0, fmt.Errorf("failed to decode private exponent: %w", err)
	}
	pBytes, err := base64URLDecode(jwk.P)
	if err != nil {
		return nil, UnknownCryptoKeyType, 0, fmt.Errorf("failed to decode first prime factor: %w", err)
	}
	qBytes, err := base64URLDecode(jwk.Q)
	if err != nil {
		return nil, UnknownCryptoKeyType, 0, fmt.Errorf("failed to decode second prime factor: %w", err)
	}
	dpBytes, err := base64URLDecode(jwk.Dp)
	if err != nil {
		return nil, UnknownCryptoKeyType, 0, fmt.Errorf("failed to decode first exponent: %w", err)
	}
	dqBytes, err := base64URLDecode(jwk.Dq)
	if err != nil {
		return nil, UnknownCryptoKeyType, 0, fmt.Errorf("failed to decode second exponent: %w", err)
	}
	qiBytes, err := base64URLDecode(jwk.Qi)
	if err != nil {
		return nil, UnknownCryptoKeyType, 0, fmt.Errorf("failed to decode coefficient: %w", err)
	}

	privKey := &rsa.PrivateKey{
		PublicKey: pubKey,
		D:         new(big.Int).SetBytes(dBytes),
		Primes: []*big.Int{
			new(big.Int).SetBytes(pBytes),
			new(big.Int).SetBytes(qBytes),
		},
		Precomputed: rsa.PrecomputedValues{

View on GitHub (pinned to 93accf6570)

Solutions

  1. Convert dp (and dq, qi) to unpadded base64url
  2. Strip whitespace and '=' padding from all CRT fields
  3. If CRT parameters are unavailable, note that k6's importer requires them for private RSA JWKs — obtain a complete JWK
  4. Validate all fields in one pass with a base64url regex before calling importKey

Example fix

// before
const jwk = { kty: 'RSA', n, e: 'AQAB', d, p, q, dp: paddedDp, dq: paddedDq, qi: paddedQi };
// after
const toB64u = (s) => s.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '').replace(/\s+/g, '');
const jwk = { kty: 'RSA', n, e: 'AQAB', d, p, q, dp: toB64u(paddedDp), dq: toB64u(paddedDq), qi: toB64u(paddedQi) };
Defensive patterns

Strategy: validation

Validate before calling

const B64URL = /^[A-Za-z0-9_-]+$/;
for (const f of ['dp', 'dq', 'qi']) {
  if (jwk[f] !== undefined) {
    const v = String(jwk[f]).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '').replace(/\s+/g, '');
    if (!B64URL.test(v)) throw new Error(f + ' is not unpadded base64url');
    jwk[f] = v;
  }
}

Type guard

function hasB64uCrtParams(jwk) {
  const ok = (s) => typeof s === 'string' && /^[A-Za-z0-9_-]+$/.test(s);
  return ok(jwk.dp) && ok(jwk.dq) && ok(jwk.qi);
}

Try / catch

try {
  key = await crypto.subtle.importKey('jwk', jwk, rsaAlg, true, usages);
} catch (e) {
  if (e.message.includes('failed to decode first exponent')) {
    jwk = { ...jwk, dp: toB64u(jwk.dp), dq: toB64u(jwk.dq), qi: toB64u(jwk.qi) };
    key = await crypto.subtle.importKey('jwk', jwk, rsaAlg, true, usages);
  } else throw e;
}

Prevention

When it happens

Trigger: dp in padded or standard base64; dp truncated; whitespace inside dp; a JWK that includes d/p/q but omits or mangles dp while still declaring a private key.

Common situations: Private JWKs from providers that use padded base64url; partial JWKs where CRT parameters were dropped or corrupted; values round-tripped through systems that alter padding.

Understand the failure class

Related errors


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