{"record":{"id":"d88664efcaa44fa4","repo":"grafana/k6","slug":"encrypted-pkcs8-formatted-key-is-not-supported","errorCode":null,"errorMessage":"encrypted pkcs8 formatted key is not supported","messagePattern":"encrypted pkcs8 formatted key is not supported","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/grpc/client.go","lineNumber":136,"sourceCode":"\n\tfdset := &descriptorpb.FileDescriptorSet{}\n\tif err = proto.Unmarshal(fdsetBytes, fdset); err != nil {\n\t\treturn nil, fmt.Errorf(\"couldn't unmarshal protoset file %s: %w\", protosetPath, err)\n\t}\n\n\treturn c.convertToMethodInfo(fdset)\n}\n\n// Note: this function was lifted from `lib/options.go`\nfunc decryptPrivateKey(key, password []byte) ([]byte, error) {\n\tblock, _ := pem.Decode(key)\n\tif block == nil {\n\t\treturn nil, errors.New(\"failed to decode PEM key\")\n\t}\n\n\tblockType := block.Type\n\tif blockType == \"ENCRYPTED PRIVATE KEY\" {\n\t\treturn nil, errors.New(\"encrypted pkcs8 formatted key is not supported\")\n\t}\n\t/*\n\t   Even though `DecryptPEMBlock` has been deprecated since 1.16.x it is still\n\t   being used here because it is deprecated due to it not supporting *good* cryptography\n\t   ultimately though we want to support something so we will be using it for now.\n\t*/\n\tdecryptedKey, err := x509.DecryptPEMBlock(block, password) //nolint:staticcheck\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkey = pem.EncodeToMemory(&pem.Block{\n\t\tType:  blockType,\n\t\tBytes: decryptedKey,\n\t})\n\treturn key, nil\n}\n\nfunc buildTLSConfig(","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/grafana/k6/blob/8d06114777191d21ef885d05c5f8aa9e1ed838ae/internal/js/modules/k6/grpc/client.go#L118-L154","documentation":"With a non-empty tls.password, k6's decryptPrivateKey handles encrypted PEM keys using x509.DecryptPEMBlock, which only understands the legacy PEM encryption format (blocks carrying a DEK-Info header, i.e. 'BEGIN RSA PRIVATE KEY'/'BEGIN EC PRIVATE KEY' with traditional encryption). A block explicitly typed 'ENCRYPTED PRIVATE KEY' is PKCS#8 encryption, a different container that the deprecated API cannot decrypt, so k6 rejects it up front rather than producing garbage.","triggerScenarios":"client.connect(addr, { tls: { key: <pem starting with '-----BEGIN ENCRYPTED PRIVATE KEY-----'>, password: '...' } }). Such keys come from 'openssl pkcs8 -topk8 -passout ...' or modern OpenSSL 3 defaults when writing encrypted keys.","commonSituations":"Security teams mandating PKCS#8 encrypted client keys; converting keys with 'openssl pkcs8 -topk8' and using the output unchanged in k6; same limitation exists in k6's TLS options (--tls) since the function was lifted from lib/options.go.","solutions":["Convert to the traditional encrypted PEM format k6 supports: 'openssl rsa -aes256 -in enc8.key -out trad.key' (prompts for passwords), or for EC keys 'openssl ec -aes256 -in enc8.key -out trad.key'.","Or decrypt the key entirely and drop the password from connect params: 'openssl pkcs8 -in enc8.key -out plain.key' then pass it without tls.password (unencrypted PKCS#8 is fine; only the ENCRYPTED variant is rejected).","Prefer keeping keys unencrypted on disk with restricted permissions in CI, letting the pipeline inject them, to avoid the legacy-format dance altogether."],"exampleFix":"# before: encrypted PKCS#8 (unsupported)\nopenssl pkcs8 -topk8 -in key.pem -out enc.key -passout pass:secret\n# tls: { key: open('enc.key'), password: 'secret' }  -> error\n\n# after: traditional encrypted PEM\nopenssl rsa -aes256 -in key.pem -out trad.key -passout pass:secret\n# tls: { key: open('trad.key'), password: 'secret' }  -> ok","handlingStrategy":"validation","validationCode":"const isEncryptedPkcs8 = (pem) => pem.includes('-----BEGIN ENCRYPTED PRIVATE KEY-----');\nconst keyPem = open('./client.key');\nif (isEncryptedPkcs8(keyPem)) {\n  throw new Error('k6 cannot use encrypted PKCS#8 keys; convert with: openssl rsa -aes256 -in client.key -out client.trad.key');\n}","typeGuard":"const isSupportedEncryptedPem = (pem) => /-----BEGIN (RSA|EC|DSA)? ?PRIVATE KEY-----/.test(pem) && !pem.includes('ENCRYPTED PRIVATE KEY');","tryCatchPattern":"try {\n  client.connect(addr, { tls: { cert, key, password } });\n} catch (e) {\n  if (/encrypted pkcs8 formatted key is not supported/.test(e.message)) {\n    throw new Error('Convert the key: openssl rsa -aes256 -in enc8.key -out trad.key');\n  }\n  throw e;\n}","preventionTips":["Avoid 'openssl pkcs8 -topk8' with -passout for keys used with k6.","Prefer unencrypted keys with tight file permissions in CI, then omit the password.","Check the PEM header line of every encrypted key before wiring it into tls params."],"tags":["k6","grpc","tls","pkcs8","private-key","encryption"],"backgroundTag":"unsupported-key-format","analyzedSha":"8d06114777191d21ef885d05c5f8aa9e1ed838ae","analyzedAt":"2026-08-18T03:05:52.393Z","contentChangedAt":"2026-08-18T03:05:52.393Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}