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
- Regenerate the whole JWK pair with crypto.subtle.generateKey and crypto.subtle.exportKey('jwk', ...) so all CRT members are consistently encoded
- Re-encode dq as unpadded base64url: strip '=', then translate '+'->'-' and '/'->'_'
- Verify each JWK member decodes before import (see validation snippet) and print which field fails
- 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
- Always generate JWKs via crypto.subtle.exportKey rather than assembling them
- Store keys in their original encoded form; never round-trip through editors that alter base64
- Unit-test your key fixtures once at script start so failures are loud and early
- Prefer PKCS8/SPKI PEM import when the key crosses systems
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to decode coefficient: %w
- failed to decode modulus: %w
- failed to decode exponent: %w
- failed to decode private exponent: %w
- failed to decode first prime factor: %w
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/e2300410a67fa74c.
Report an issue: GitHub.