grafana/k6 · error
failed to decode exponent: %w
Error message
failed to decode exponent: %w
What it means
The RSA public exponent e is decoded with base64.RawURLEncoding during JWK import. This error means e is not a valid unpadded base64url string — most commonly because it was supplied as a plain integer rendered as digits (digits are legal base64url characters, so it may decode but the key will be wrong) with padding, or contains '+'/'/' characters or whitespace that make decoding fail outright.
Source
Thrown at internal/js/modules/k6/webcrypto/jwk.go:341
func importRSAJWK(jsonKeyData []byte) (any, CryptoKeyType, int, error) {
var jwk rsaJWK
if err := json.Unmarshal(jsonKeyData, &jwk); err != nil {
return nil, UnknownCryptoKeyType, 0, fmt.Errorf("failed to parse input as RSA JWK key: %w", err)
}
if err := jwk.validate(); err != nil {
return nil, UnknownCryptoKeyType, 0, fmt.Errorf("invalid RSA JWK key: %w", err)
}
// decode the various key components
nBytes, err := base64URLDecode(jwk.N)
if err != nil {
return nil, UnknownCryptoKeyType, 0, fmt.Errorf("failed to decode modulus: %w", err)
}
eBytes, err := base64URLDecode(jwk.E)
if err != nil {
return nil, UnknownCryptoKeyType, 0, fmt.Errorf("failed to decode exponent: %w", err)
}
// 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)
}View on GitHub (pinned to 93accf6570)
Solutions
- Encode e as unpadded base64url of the big-endian integer ('AQAB' for 65537)
- Never paste hex text ('10001') or a decimal number as e
- Strip padding and whitespace from the value
- Verify by decoding: base64url-decoded e should be 3 bytes 01 00 01 for the standard exponent
Example fix
// before
const jwk = { kty: 'RSA', n: b64uN, e: '==AQAB==' };
// after
const jwk = { kty: 'RSA', n: b64uN, e: 'AQAB' }; Defensive patterns
Strategy: validation
Validate before calling
const B64URL = /^[A-Za-z0-9_-]+$/;
const e = String(jwk.e).replace(/=+$/, '');
if (!B64URL.test(e)) throw new Error('exponent e is not unpadded base64url');
if (e === '10001') throw new Error('e looks like hex text; use "AQAB"');
jwk = { ...jwk, e }; Type guard
function isB64uExponent(s) {
return typeof s === 'string' && /^[A-Za-z0-9_-]+$/.test(s) && !/^\d+$/.test(s) && s.length >= 1;
} Try / catch
try {
key = await crypto.subtle.importKey('jwk', jwk, rsaAlg, true, usages);
} catch (e) {
if (err.message.includes('failed to decode exponent')) {
jwk = { ...jwk, e: 'AQAB' }; // standard exponent
key = await crypto.subtle.importKey('jwk', jwk, rsaAlg, true, usages);
} else throw err;
} Prevention
- Encode e as base64url of the big-endian integer ('AQAB' for 65537)
- Reject pure-digit e values in fixture validation — they are hex or decimal text
- Keep the same encoder for n and e
When it happens
Trigger: e given as padded base64url; e containing '+' or '/'; e with whitespace. Note e = 65537 must appear as 'AQAB', while '10001' (hex text) would decode as garbage bytes rather than error — check the wrapped cause for the exact failure.
Common situations: Exponent taken as hex text '10001' with padding appended; JWK builders that mix encodings between n and e; copy-paste from documentation showing different encodings.
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 modulus: %w
- failed to decode private exponent: %w
- failed to decode first prime factor: %w
- failed to decode second prime factor: %w
- failed to decode first exponent: %w
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/7c915ad2dbf30ec9.
Report an issue: GitHub.