grafana/k6 · error

failed to decode first prime factor: %w

Error message

failed to decode first prime factor: %w

What it means

For RSA private-key JWK import, the first prime factor p is decoded with base64.RawURLEncoding. This error indicates p is not valid unpadded base64url (padding, '+'/'/' characters, whitespace, or bad length). Note the field is only read once d is present, so public-key imports never hit it.

Source

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

	// convert exponent to an integer
	eInt := new(big.Int).SetBytes(eBytes).Int64()
	pubKey := rsa.PublicKey{
		N: new(big.Int).SetBytes(nBytes),
		E: int(eInt),
	}

	// if the private exponent is missing, return the public key
	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)
	}

View on GitHub (pinned to 93accf6570)

Solutions

  1. Convert p to unpadded base64url
  2. Strip whitespace/newlines from the value
  3. Verify the decoded length is roughly half the modulus size (128 bytes for RSA-2048)
  4. Re-export the full private JWK from the original key material rather than assembling field by field

Example fix

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

Strategy: validation

Validate before calling

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

Type guard

function isB64uPrime(s) {
  return typeof s === 'string' && /^[A-Za-z0-9_-]+$/.test(s) && s.length >= 20;
}

Try / catch

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

Prevention

When it happens

Trigger: p in padded or standard base64; p truncated; p missing-but-typed incorrectly (an empty p decodes as empty bytes and fails later at key validation, not here); whitespace inside p.

Common situations: Private JWKs assembled from PEM-derived values; primes re-encoded by intermediate systems; multi-line PEM body mistakenly used as p.

Understand the failure class

Related errors


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