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
- Convert p to unpadded base64url
- Strip whitespace/newlines from the value
- Verify the decoded length is roughly half the modulus size (128 bytes for RSA-2048)
- 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
- Normalize all private fields (d, p, q, dp, dq, qi) together in one pass
- Do not assemble private JWKs from PEM dumps field by field; export a complete JWK
- Check p decodes to about half the modulus length
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to decode private exponent: %w
- failed to decode second prime factor: %w
- failed to decode first exponent: %w
- failed to decode D: %w
- failed to decode modulus: %w
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/9235e1e263fab50d.
Report an issue: GitHub.