---
title: "Generate a key and a PKCS #12 file - PKI Express"
description: "How to generate an RSA key pair and a CSR with PKI Express (KeyGenerator, gen-key) and combine the key with the issued certificate into a PKCS #12 (.pfx) file (Pkcs12Generator, create-pfx), in PHP, Java, Node.js and Python."
canonical: "https://docs.lacunasoftware.com/en-us/articles/pki-express/key-generation"
locale: "en"
product: "PKI Express"
last_updated: "2026-10-19"
---

# Generate a key and a PKCS #12 file - PKI Express

[PKI Express](https://docs.lacunasoftware.com/en-us/articles/pki-express) can make an RSA key pair on your server, with a certificate signing request (CSR) for it, and, once a CA has
issued the certificate, combine the key and the certificate into a PKCS #12 (`.pfx`) file that you can [sign with](https://docs.lacunasoftware.com/en-us/articles/pki-express/pades-signature#server-key).
Two classes do this: `KeyGenerator`, which runs the `gen-key` command, and `Pkcs12Generator`, which runs `create-pfx`. 

* Both commands need PKI Express 1.11.0 or later. The client libraries ask for that version; with an older PKI Express, the operation fails
  with a tip naming the version (see [Return codes](https://docs.lacunasoftware.com/en-us/articles/pki-express/return-codes#client-libraries)). 
* The Java, Node.js, PHP and Python libraries have these classes; the Ruby library (`pki_express` 1.3.2) does not. 
* The [PKI Suite samples](https://github.com/LacunaSoftware/PkiSuiteSamples) use them to issue a certificate with [Amplia](https://docs.lacunasoftware.com/en-us/articles/amplia/integration/issue-from-csr)
  and then sign a PDF with it (*Issue a certificate with the key on the server* and *Sign a PDF with a server key*). 

## Flow

1. `KeyGenerator.generate()` with `genCsr` set returns the private key and a CSR.
1. Store the key on your server and send the CSR to your CA, such as Amplia, which returns the certificate.
1. `Pkcs12Generator.generate()` takes the key and the certificate file and returns the PKCS #12 file, protected by the password you give it.

In PHP, with the `lacuna/pki-express` package (1.17.0): 

```php
use Lacuna\PkiExpress\KeyGenerator;
use Lacuna\PkiExpress\KeyFormats;
use Lacuna\PkiExpress\Pkcs12Generator;
use Lacuna\PkiExpress\SupportedKeySizes;

// 1. Make the key pair and the CSR.
$keyGenerator = new KeyGenerator();
$keyGenerator->keySize = SupportedKeySizes::S2048;
$keyGenerator->keyFormat = KeyFormats::JSON;
$keyGenerator->genCsr = true;
$genKey = $keyGenerator->generate();
// $genKey->key: the private key, as a string in the chosen format
// $genKey->csr: the PKCS #10 CSR, PEM-encoded

// 2. Store $genKey->key (encrypted) and send $genKey->csr to the CA,
//    which returns the certificate, saved here as $certPath.

// 3. Make the PKCS #12 file.
$pkcs12Generator = new Pkcs12Generator();
$pkcs12Generator->key = $genKey->key;
$pkcs12Generator->setCertFile($certPath);
$result = $pkcs12Generator->generate($password);
$result->pfx->writeToFile('/path/to/signer.pfx'); // or getContentRaw(), getContentBase64()
```

The samples call `Util::setPkiDefaults()` on both objects, as on every PKI Express operator, and keep the key and the certificate in
storage between steps 1 and 3. 
They warn that the private key should be encrypted before it is stored. 

## Key generation options

| Option | PHP | Values | Default |
|---|---|---|---|
| Key size, in bits | `keySize` | `SupportedKeySizes::S1024`, `S2048`, `S4096` (1024, 2048, 4096) | 2048 |
| Key format | `keyFormat`, or the argument of `generate()` | `KeyFormats::JSON` (`json`), `KeyFormats::BLOB` (`blob`), `KeyFormats::XML` (`xml`) | `json` |
| CSR | `genCsr` | `true` to make a CSR; leave it unset for the key alone | no CSR |

* The key is RSA. Only 1024, 2048 and 4096 bits are supported: the client libraries reject another size before running PKI Express
  (PHP throws a `RuntimeException`), and PKI Express itself returns `36 - KeySizeNotSupported`. 
* The three formats hold the same private key, serialized differently: `json` is a JSON object of the RSA parameters, `xml` an
  `<RSAKeyValue>` document, and `blob` a Base64 CSP key blob. The samples use `json`. 
* The CSR is a PKCS #10 request, PEM-encoded, signed with SHA-256 with RSA; it carries only the public key, so the certificate's subject
  comes from the CA (in Amplia, from the order). 
* `generate()` returns a `KeyGenerationResult` with two strings: `key` and `csr` (`null` without `genCsr`). 

## PKCS #12 options

| Input | PHP |
|---|---|
| The key, as returned by `KeyGenerator`, in any of the three formats | `key` property (or the first constructor argument) |
| The certificate file | `setCertFile($path)` (alias of `setCertFileFromPath`), `setCertFileFromContentRaw($bytes)`, `setCertFileFromContentBase64($base64)` |
| The password of the PKCS #12 file | argument of `generate($password)`, or the `password` property |

* PKI Express recognizes the key format by its content; a key it cannot read gives `37 - BadKeyFormat` or `38 - BadKey`. 
* Without a password, the PKCS #12 file is created with an empty password. 
* `generate()` returns a `Pkcs12GenerationResult` whose `pfx` is a `Pkcs12Certificate`: `getContentRaw()` gives the bytes,
  `getContentBase64()` the Base64, and `writeToFile($path)` saves it. To sign with it, pass the bytes to `setPkcs12Content()` and the
  password to `setCertPassword()`, as the PAdES server-key sample does. 
* The client library passes the key and the password to `pkie` as command-line arguments. 
* If you would rather have the CA make the key, Amplia can return the PKCS #12 file itself: see [Issue a PKCS #12 file](https://docs.lacunasoftware.com/en-us/articles/amplia/integration/pkcs12).

## In each language

The classes have the same names in every library that has them; the setters and the result objects differ: 

| Library | Key options | Key result | Certificate and password | PKCS #12 result |
|---|---|---|---|---|
| PHP | `keySize`, `keyFormat`, `genCsr` | `$r->key`, `$r->csr` | `key`, `setCertFile($path)`, `generate($password)` | `$r->pfx->getContentRaw()`, `writeToFile($path)` |
| Java | `setKeySize(int)`, `setKeyFormat(KeyFormats)`, `setGenCsr(boolean)` | `getKey()`, `getCsr()` | `setKey`, `setCertFile(path)` (also `byte[]`, `InputStream`), `generate(password)` | `getPfxContent()` (`byte[]`), `getPfxContentBase64()` |
| Node.js | constructor `{ keySize, keyFormat, genCsr }`, or the setters | `key`, `csr` | constructor `{ key }`, `setCertFileFromPathSync(path)`, `generate(password)` | `pfx.contentRaw` (`Buffer`), `pfx.contentBase64`, `pfx.writeToFileSync(path)` |
| Python | `key_size`, `key_format`, `gen_csr` | `key`, `csr` | `key`, `set_cert_file_from_path(path)`, `generate(password)` | `pfx.content` (`bytes`), `pfx.write_to_file(path)` |

* Java, Node.js and Python default to a 2048-bit key in `json` format; PHP sends no option, so PKI Express applies the same defaults. 
* In Python, set `key_size` with the `SupportedKeySizes` constants, which are strings (`'2048'`); in Node.js, `keySize` takes `2048` or `'2048'`. 
* `KeyFormats` is exported by every library; `SupportedKeySizes` by PHP and Python. 
* In Node.js, `generate()` returns a promise. 

**Java**

```java
KeyGenerator keyGenerator = new KeyGenerator();
keyGenerator.setKeySize(2048);
keyGenerator.setKeyFormat(KeyFormats.JSON);
keyGenerator.setGenCsr(true);
KeyGenerationResult genKey = keyGenerator.generate();
// genKey.getKey(), genKey.getCsr()

Pkcs12Generator pkcs12Generator = new Pkcs12Generator();
pkcs12Generator.setKey(keyJson); // the key from genKey.getKey(), read back from storage
pkcs12Generator.setCertFile(certPath);
Pkcs12GenerationResult pkcs12GenResult = pkcs12Generator.generate(password);
signer.setPkcs12(pkcs12GenResult.getPfxContent());
signer.setCertPassword(password);
```

**Node.js**

```js
const { KeyGenerator, KeyFormats, Pkcs12Generator } = require('pki-express');

const keyGenerator = new KeyGenerator({ keySize: 2048, keyFormat: KeyFormats.JSON, genCsr: true });
const genKey = await keyGenerator.generate();
// genKey.key, genKey.csr

const pkcs12Generator = new Pkcs12Generator({ key: keyJson }); // the key from genKey.key, read back from storage
pkcs12Generator.setCertFileFromPathSync(certPath);
const pkcs12GenResult = await pkcs12Generator.generate(password);
await signer.setPkcs12FromRaw(pkcs12GenResult.pfx.contentRaw);
signer.certPassword = password;
```

**Python**

```python
from pkiexpress import KeyGenerator, Pkcs12Generator, SupportedKeySizes

key_generator = KeyGenerator()
key_generator.key_size = SupportedKeySizes.S2048
key_generator.gen_csr = True
gen_key = key_generator.generate()
# gen_key.key, gen_key.csr

pkcs12_generator = Pkcs12Generator()
pkcs12_generator.key = gen_key.key
pkcs12_generator.set_cert_file_from_base64(cert.content_base64)
pkcs12_gen_result = pkcs12_generator.generate('1234')
pkcs12_gen_result.pfx.write_to_file('signer.pfx')
```

## Errors

A failed command gives one of the PKI Express [return codes](https://docs.lacunasoftware.com/en-us/articles/pki-express/return-codes): `36 - KeySizeNotSupported`, `37 - BadKeyFormat` or
`38 - BadKey` for these commands, `04 - BadCertificate` when the certificate file cannot be decoded. In PHP, catch `CommandException`
and read `$e->returnCode`: see [Return codes in the client libraries](https://docs.lacunasoftware.com/en-us/articles/pki-express/return-codes#client-libraries). 

TODO(docs): whether `create-pfx` checks that the certificate matches the key, and which return code it gives when they do not match.

## See also

* [Sign a PDF with a server key](https://docs.lacunasoftware.com/en-us/articles/pki-express/pades-signature#server-key)
* [Issue from a CSR](https://docs.lacunasoftware.com/en-us/articles/amplia/integration/issue-from-csr) with Amplia
* [PKI Express quick reference](https://docs.lacunasoftware.com/en-us/articles/pki-express/quick-reference)
