grafana/k6 · error

failed to decode second exponent: %w

Error message

failed to decode second exponent: %w

What it means

Thrown by k6's webcrypto RSA JWK importer. When importing an RSA private key with crypto.subtle.importKey('jwk', ...), k6 base64url-decodes every CRT field; this error means the 'dq' (second exponent / d mod q-1) member is not valid unpadded base64url. The underlying decode error is wrapped, so the message ends with the exact base64 failure reason.

Source

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

	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{
			Dp:   new(big.Int).SetBytes(dpBytes),
			Dq:   new(big.Int).SetBytes(dqBytes),
			Qinv: new(big.Int).SetBytes(qiBytes),
		},

View on GitHub (pinned to 93accf6570)

Solutions

  1. Regenerate the whole JWK pair with crypto.subtle.generateKey and crypto.subtle.exportKey('jwk', ...) so all CRT members are consistently encoded
  2. Re-encode dq as unpadded base64url: strip '=', then translate '+'->'-' and '/'->'_'
  3. Verify each JWK member decodes before import (see validation snippet) and print which field fails
  4. If the key material is unreliable, import from PEM/PKCS8/SPKI instead of JWK

Example fix

// before
const key = await crypto.subtle.importKey('jwk', jwk, alg, true, ['decrypt']);
// jwk.dq was standard base64: 'AbC+d/...='

// after: normalize every member to unpadded base64url
const b64url = s => s.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const clean = { ...jwk, dq: b64url(jwk.dq) };
const key = await crypto.subtle.importKey('jwk', clean, alg, true, ['decrypt']);
Defensive patterns

Strategy: validation

Validate before calling

const B64URL_RE = /^[A-Za-z0-9_-]*$/;
function assertValidRsaJwk(jwk) {
  for (const f of ['n','e','d','p','q','dp','dq','qi']) {
    const v = jwk[f];
    if (typeof v !== 'string' || !B64URL_RE.test(v)) {
      throw new Error(`JWK member '${f}' is not unpadded base64url`);
    }
  }
}
assertValidRsaJwk(jwk);
await crypto.subtle.importKey('jwk', jwk, alg, true, usages);

Type guard

const isB64Url = s => typeof s === 'string' && /^[A-Za-z0-9_-]*$/.test(s);
const hasValidRsaCrt = j => ['n','e','d','p','q','dp','dq','qi'].every(k => isB64Url(j[k]));

Try / catch

try { return await crypto.subtle.importKey('jwk', jwk, alg, true, usages); }
catch (e) { throw new Error(`bad JWK (${e.message}); re-export from a trusted key`); }

Prevention

When it happens

Trigger: Calling crypto.subtle.importKey('jwk', jwk, {name:'RSA-OAEP'|'RSA-PSS'|'RSASSA-PKCS1-v1_5', ...}, ...) where jwk.dq contains characters outside the base64url alphabet (e.g. '+', '/', or non-ASCII) or has the wrong length/padding ('=' is not accepted). Fields n, e, d, p, q, and dp must already decode cleanly or an earlier error fires instead.

Common situations: Hand-editing or copy-pasting a JWK and corrupting the dq value; converting a JWK from standard base64 to base64url but leaving '+'/'/' or '=' in place; generating the JWK with a tool that emits padded standard base64 instead of RFC 7515 base64url; passing a public-only JWK where dq is garbage rather than empty.

Understand the failure class

Related errors


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