grafana/k6 · error · ErrInvalidPkcs7Data
invalid PKCS7 data
Error message
invalid PKCS7 data
What it means
ErrInvalidPkcs7Data is returned by pKCS7Pad (internal/js/modules/k6/webcrypto/aes.go:673) when the plaintext is empty: `if len(plaintext) == 0`. The AES-CBC implementation in k6 calls pKCS7Pad unconditionally (aes.go:286), so encrypting a zero-length buffer with AES-CBC fails instead of producing the all-padding ciphertext that other WebCrypto implementations return. It is a k6-specific limitation of the AES-CBC encrypt operation.
Source
Thrown at internal/js/modules/k6/webcrypto/aes.go:661
// maxAESGcmAdditionalDataLength holds the value 2 ^ 64 - 1 as specified in
// the [Web Crypto API spec] for the AES-GCM algorithm encryption operation.
//
// [Web Crypto API spec]: https://www.w3.org/TR/WebCryptoAPI/#aes-gcm-encryption-operation
const maxAESGcmAdditionalDataLength uint64 = (1 << 64) - 1
// maxAESGcmIvLength holds the value 2 ^ 64 - 1 as specified in
// the [Web Crypto API spec] for the AES-GCM algorithm encryption operation.
//
// [Web Crypto API spec]: https://www.w3.org/TR/WebCryptoAPI/#aes-gcm-encryption-operation
const maxAESGcmIvLength uint64 = (1 << 64) - 1
var (
// ErrInvalidBlockSize is returned when the given block size is invalid.
ErrInvalidBlockSize = errors.New("invalid block size")
// ErrInvalidPkcs7Data is returned when the given data is invalid.
ErrInvalidPkcs7Data = errors.New("invalid PKCS7 data")
)
// pKCS7Padding adds PKCS7 padding to the given plaintext.
// It implements section 10.3 of [RFC 2315].
//
// [RFC 2315]: https://www.rfc-editor.org/rfc/rfc2315#section-10.3
func pKCS7Pad(plaintext []byte, blockSize int) ([]byte, error) {
if blockSize <= 0 {
return nil, ErrInvalidBlockSize
}
if len(plaintext) == 0 {
return nil, ErrInvalidPkcs7Data
}
l := len(plaintext)
padding := blockSize - (l % blockSize)
paddingText := bytes.Repeat([]byte{byte(padding)}, padding) //nolint:gosecView on GitHub (pinned to 93accf6570)
Solutions
- Guard the empty case before encrypting: return early, or encrypt a sentinel byte
- Switch the algorithm to AES-GCM, which handles zero-length plaintext in k6
- If ciphertext compatibility matters, handle empty plaintext as a special case in your protocol
Example fix
// before
const ct = await crypto.subtle.encrypt({ name: 'AES-CBC', iv }, key, new Uint8Array(0));
// after
const data = new Uint8Array(0);
const ct = data.length === 0
? new ArrayBuffer(0) // protocol-level sentinel for empty payload
: await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, data); Defensive patterns
Strategy: validation
Validate before calling
const data = new Uint8Array(raw);
if (data.byteLength === 0) throw new Error('AES-CBC in k6 cannot encrypt empty plaintext; use AES-GCM or a sentinel'); Try / catch
try {
ct = await crypto.subtle.encrypt({ name: 'AES-CBC', iv }, key, data);
} catch (e) {
if (String(e.message).includes('invalid PKCS7 data') && data.byteLength === 0) {
ct = new ArrayBuffer(0); // app-level convention for empty payloads
} else { throw e; }
} Prevention
- Never pass zero-length data to AES-CBC encrypt in k6
- Prefer AES-GCM for new code — it accepts empty payloads and is authenticated
- Add an explicit byteLength check at crypto helpers' entry points
When it happens
Trigger: `await crypto.subtle.encrypt({ name: 'AES-CBC', iv }, key, new Uint8Array(0))` or passing an empty ArrayBuffer/TypedArray as data with AES-CBC. Other modes (e.g. AES-GCM) accept empty data fine.
Common situations: Encrypting request payloads that are legitimately empty (empty POST bodies, empty JSON); edge-case handling in crypto round-trip tests; code ported from Node.js or browsers where empty plaintext is accepted.
Related errors
- k6/experimental/webcrypto has been graduated and it's now gl
- invalid block size
- invalid ECDH curve
- invalid elliptic curve {k}
- unsupported algorithm {alg}
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/7eda1924c87e0a5f.
Report an issue: GitHub.