Skip to content

Commit 261b64a

Browse files
committed
fixup! crypto: add raw key formats support to the KeyObject APIs
1 parent f1d3884 commit 261b64a

File tree

1 file changed

+203
-55
lines changed

1 file changed

+203
-55
lines changed

doc/api/crypto.md

Lines changed: 203 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -75,37 +75,161 @@ try {
7575

7676
## Asymmetric key types
7777

78-
The following table lists the asymmetric key types recognized by the [`KeyObject`][] API:
79-
80-
| Key Type | Description | OID |
81-
| ---------------------------------- | ------------------ | ----------------------- |
82-
| `'dh'` | Diffie-Hellman | 1.2.840.113549.1.3.1 |
83-
| `'dsa'` | DSA | 1.2.840.10040.4.1 |
84-
| `'ec'` | Elliptic curve | 1.2.840.10045.2.1 |
85-
| `'ed25519'` | Ed25519 | 1.3.101.112 |
86-
| `'ed448'` | Ed448 | 1.3.101.113 |
87-
| `'ml-dsa-44'`[^openssl35] | ML-DSA-44 | 2.16.840.1.101.3.4.3.17 |
88-
| `'ml-dsa-65'`[^openssl35] | ML-DSA-65 | 2.16.840.1.101.3.4.3.18 |
89-
| `'ml-dsa-87'`[^openssl35] | ML-DSA-87 | 2.16.840.1.101.3.4.3.19 |
90-
| `'ml-kem-512'`[^openssl35] | ML-KEM-512 | 2.16.840.1.101.3.4.4.1 |
91-
| `'ml-kem-768'`[^openssl35] | ML-KEM-768 | 2.16.840.1.101.3.4.4.2 |
92-
| `'ml-kem-1024'`[^openssl35] | ML-KEM-1024 | 2.16.840.1.101.3.4.4.3 |
93-
| `'rsa-pss'` | RSA PSS | 1.2.840.113549.1.1.10 |
94-
| `'rsa'` | RSA | 1.2.840.113549.1.1.1 |
95-
| `'slh-dsa-sha2-128f'`[^openssl35] | SLH-DSA-SHA2-128f | 2.16.840.1.101.3.4.3.21 |
96-
| `'slh-dsa-sha2-128s'`[^openssl35] | SLH-DSA-SHA2-128s | 2.16.840.1.101.3.4.3.20 |
97-
| `'slh-dsa-sha2-192f'`[^openssl35] | SLH-DSA-SHA2-192f | 2.16.840.1.101.3.4.3.23 |
98-
| `'slh-dsa-sha2-192s'`[^openssl35] | SLH-DSA-SHA2-192s | 2.16.840.1.101.3.4.3.22 |
99-
| `'slh-dsa-sha2-256f'`[^openssl35] | SLH-DSA-SHA2-256f | 2.16.840.1.101.3.4.3.25 |
100-
| `'slh-dsa-sha2-256s'`[^openssl35] | SLH-DSA-SHA2-256s | 2.16.840.1.101.3.4.3.24 |
101-
| `'slh-dsa-shake-128f'`[^openssl35] | SLH-DSA-SHAKE-128f | 2.16.840.1.101.3.4.3.27 |
102-
| `'slh-dsa-shake-128s'`[^openssl35] | SLH-DSA-SHAKE-128s | 2.16.840.1.101.3.4.3.26 |
103-
| `'slh-dsa-shake-192f'`[^openssl35] | SLH-DSA-SHAKE-192f | 2.16.840.1.101.3.4.3.29 |
104-
| `'slh-dsa-shake-192s'`[^openssl35] | SLH-DSA-SHAKE-192s | 2.16.840.1.101.3.4.3.28 |
105-
| `'slh-dsa-shake-256f'`[^openssl35] | SLH-DSA-SHAKE-256f | 2.16.840.1.101.3.4.3.31 |
106-
| `'slh-dsa-shake-256s'`[^openssl35] | SLH-DSA-SHAKE-256s | 2.16.840.1.101.3.4.3.30 |
107-
| `'x25519'` | X25519 | 1.3.101.110 |
108-
| `'x448'` | X448 | 1.3.101.111 |
78+
The following table lists the asymmetric key types recognized by the [`KeyObject`][] API
79+
and the export/import formats supported for each key type.
80+
81+
| Key Type | Description | OID | `'pem'` | `'der'` | `'jwk'` | `'raw-public'` | `'raw-private'` | `'raw-seed'` |
82+
| ---------------------------------- | ------------------ | ----------------------- | ------- | ------- | ------- | -------------- | --------------- | ------------ |
83+
| `'dh'` | Diffie-Hellman | 1.2.840.113549.1.3.1 ||| | | | |
84+
| `'dsa'` | DSA | 1.2.840.10040.4.1 ||| | | | |
85+
| `'ec'` | Elliptic curve | 1.2.840.10045.2.1 |||||| |
86+
| `'ed25519'` | Ed25519 | 1.3.101.112 |||||| |
87+
| `'ed448'` | Ed448 | 1.3.101.113 |||||| |
88+
| `'ml-dsa-44'`[^openssl35] | ML-DSA-44 | 2.16.840.1.101.3.4.3.17 ||||| ||
89+
| `'ml-dsa-65'`[^openssl35] | ML-DSA-65 | 2.16.840.1.101.3.4.3.18 ||||| ||
90+
| `'ml-dsa-87'`[^openssl35] | ML-DSA-87 | 2.16.840.1.101.3.4.3.19 ||||| ||
91+
| `'ml-kem-512'`[^openssl35] | ML-KEM-512 | 2.16.840.1.101.3.4.4.1 ||| || ||
92+
| `'ml-kem-768'`[^openssl35] | ML-KEM-768 | 2.16.840.1.101.3.4.4.2 ||| || ||
93+
| `'ml-kem-1024'`[^openssl35] | ML-KEM-1024 | 2.16.840.1.101.3.4.4.3 ||| || ||
94+
| `'rsa-pss'` | RSA PSS | 1.2.840.113549.1.1.10 ||| | | | |
95+
| `'rsa'` | RSA | 1.2.840.113549.1.1.1 |||| | | |
96+
| `'slh-dsa-sha2-128f'`[^openssl35] | SLH-DSA-SHA2-128f | 2.16.840.1.101.3.4.3.21 ||| ||| |
97+
| `'slh-dsa-sha2-128s'`[^openssl35] | SLH-DSA-SHA2-128s | 2.16.840.1.101.3.4.3.20 ||| ||| |
98+
| `'slh-dsa-sha2-192f'`[^openssl35] | SLH-DSA-SHA2-192f | 2.16.840.1.101.3.4.3.23 ||| ||| |
99+
| `'slh-dsa-sha2-192s'`[^openssl35] | SLH-DSA-SHA2-192s | 2.16.840.1.101.3.4.3.22 ||| ||| |
100+
| `'slh-dsa-sha2-256f'`[^openssl35] | SLH-DSA-SHA2-256f | 2.16.840.1.101.3.4.3.25 ||| ||| |
101+
| `'slh-dsa-sha2-256s'`[^openssl35] | SLH-DSA-SHA2-256s | 2.16.840.1.101.3.4.3.24 ||| ||| |
102+
| `'slh-dsa-shake-128f'`[^openssl35] | SLH-DSA-SHAKE-128f | 2.16.840.1.101.3.4.3.27 ||| ||| |
103+
| `'slh-dsa-shake-128s'`[^openssl35] | SLH-DSA-SHAKE-128s | 2.16.840.1.101.3.4.3.26 ||| ||| |
104+
| `'slh-dsa-shake-192f'`[^openssl35] | SLH-DSA-SHAKE-192f | 2.16.840.1.101.3.4.3.29 ||| ||| |
105+
| `'slh-dsa-shake-192s'`[^openssl35] | SLH-DSA-SHAKE-192s | 2.16.840.1.101.3.4.3.28 ||| ||| |
106+
| `'slh-dsa-shake-256f'`[^openssl35] | SLH-DSA-SHAKE-256f | 2.16.840.1.101.3.4.3.31 ||| ||| |
107+
| `'slh-dsa-shake-256s'`[^openssl35] | SLH-DSA-SHAKE-256s | 2.16.840.1.101.3.4.3.30 ||| ||| |
108+
| `'x25519'` | X25519 | 1.3.101.110 |||||| |
109+
| `'x448'` | X448 | 1.3.101.111 |||||| |
110+
111+
### Raw key formats
112+
113+
<!-- YAML
114+
added: REPLACEME
115+
-->
116+
117+
> Stability: 1.1 - Active development
118+
119+
The `'raw-public'`, `'raw-private'`, and `'raw-seed'` key formats allow
120+
importing and exporting raw key material without any encoding wrapper.
121+
See [`keyObject.export()`][], [`crypto.createPublicKey()`][], and
122+
[`crypto.createPrivateKey()`][] for usage details.
123+
124+
Example: Exporting raw keys and importing them:
125+
126+
```mjs
127+
const crypto = await import('node:crypto');
128+
129+
const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');
130+
131+
// Export the raw public key (32 bytes for Ed25519).
132+
const rawPublicKey = publicKey.export({ format: 'raw-public' });
133+
134+
// Export the raw private key (32 bytes for Ed25519).
135+
const rawPrivateKey = privateKey.export({ format: 'raw-private' });
136+
137+
// Import the raw public key
138+
const importedPublicKey = crypto.createPublicKey({
139+
key: rawPublicKey,
140+
format: 'raw-public',
141+
asymmetricKeyType: 'ed25519',
142+
});
143+
144+
// Import the raw private key
145+
const importedPrivateKey = crypto.createPrivateKey({
146+
key: rawPrivateKey,
147+
format: 'raw-private',
148+
asymmetricKeyType: 'ed25519',
149+
});
150+
151+
// Raw keys can also be used directly with sign() and verify()
152+
// without explicitly importing them first.
153+
const data = new TextEncoder().encode('some data to sign');
154+
const signature = crypto.sign(null, data, {
155+
key: rawPrivateKey,
156+
format: 'raw-private',
157+
asymmetricKeyType: 'ed25519',
158+
});
159+
console.log(crypto.verify(null, data, {
160+
key: rawPublicKey,
161+
format: 'raw-public',
162+
asymmetricKeyType: 'ed25519',
163+
}, signature));
164+
// Prints: true
165+
```
166+
167+
Example: For EC keys, the `namedCurve` option is required when importing:
168+
169+
```mjs
170+
const crypto = await import('node:crypto');
171+
172+
const { publicKey } = crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' });
173+
174+
// Export the raw EC public key (compressed by default).
175+
const rawPublicKey = publicKey.export({ format: 'raw-public' });
176+
177+
// The following is equivalent.
178+
const rawPublicKeyCompressed = publicKey.export({
179+
format: 'raw-public',
180+
type: 'compressed',
181+
});
182+
183+
// Export uncompressed point format.
184+
const rawPublicKeyUncompressed = publicKey.export({
185+
format: 'raw-public',
186+
type: 'uncompressed',
187+
});
188+
189+
// Import the raw EC public key
190+
// Both compressed and uncompressed point formats are accepted.
191+
const importedPublicKey = crypto.createPublicKey({
192+
key: rawPublicKey,
193+
format: 'raw-public',
194+
asymmetricKeyType: 'ec',
195+
namedCurve: 'P-256',
196+
});
197+
```
198+
199+
Example: Exporting raw seeds and importing them:
200+
201+
```mjs
202+
const crypto = await import('node:crypto');
203+
204+
const { publicKey, privateKey } = crypto.generateKeyPairSync('ml-kem-768');
205+
206+
// Export the raw seed (32 bytes for ML-KEM).
207+
const seed = privateKey.export({ format: 'raw-seed' });
208+
209+
// Import the raw seed
210+
const importedPrivateKey = crypto.createPrivateKey({
211+
key: seed,
212+
format: 'raw-seed',
213+
asymmetricKeyType: 'ml-kem-768',
214+
});
215+
216+
// Export the raw public key.
217+
const rawPublicKey = publicKey.export({ format: 'raw-public' });
218+
219+
// Raw keys can also be used directly with encapsulate() and decapsulate()
220+
// without explicitly importing them first.
221+
const { ciphertext, sharedKey } = crypto.encapsulate({
222+
key: rawPublicKey,
223+
format: 'raw-public',
224+
asymmetricKeyType: 'ml-kem-768',
225+
});
226+
console.log(crypto.decapsulate({
227+
key: seed,
228+
format: 'raw-seed',
229+
asymmetricKeyType: 'ml-kem-768',
230+
}, ciphertext).equals(sharedKey));
231+
// Prints: true
232+
```
109233

110234
## Class: `Certificate`
111235

@@ -2121,6 +2245,10 @@ type, value, and parameters. This method is not
21212245
<!-- YAML
21222246
added: v11.6.0
21232247
changes:
2248+
- version: REPLACEME
2249+
pr-url: https://github.com/nodejs/node/pull/62240
2250+
description: Added support for `'raw-public'`, `'raw-private'`,
2251+
and `'raw-seed'` formats.
21242252
- version: REPLACEME
21252253
pr-url: https://github.com/nodejs/node/pull/62178
21262254
description: ML-KEM and ML-DSA private key `'pkcs8'` export now
@@ -2140,36 +2268,39 @@ For symmetric keys, the following encoding options can be used:
21402268

21412269
For public keys, the following encoding options can be used:
21422270

2143-
* `type` {string} Must be one of `'pkcs1'` (RSA only) or `'spki'`.
2144-
* `format` {string} Must be `'pem'`, `'der'`, or `'jwk'`.
2271+
* `format` {string} Must be `'pem'`, `'der'`, `'jwk'`, or `'raw-public'`.
2272+
See [asymmetric key types][] for format support.
2273+
* `type` {string} When `format` is `'pem'` or `'der'`, must be `'pkcs1'`
2274+
(RSA only) or `'spki'`. For EC keys with `'raw-public'` format, may be
2275+
`'compressed'` (default) or `'uncompressed'`. Ignored when `format` is
2276+
`'jwk'`.
21452277

21462278
For private keys, the following encoding options can be used:
21472279

2148-
* `type` {string} Must be one of `'pkcs1'` (RSA only), `'pkcs8'` or
2149-
`'sec1'` (EC only).
2150-
* `format` {string} Must be `'pem'`, `'der'`, or `'jwk'`.
2280+
* `format` {string} Must be `'pem'`, `'der'`, `'jwk'`, `'raw-private'`,
2281+
or `'raw-seed'`. See [asymmetric key types][] for format support.
2282+
* `type` {string} When `format` is `'pem'` or `'der'`, must be `'pkcs1'`
2283+
(RSA only), `'pkcs8'`, or `'sec1'` (EC only). Ignored when `format` is
2284+
`'jwk'`, `'raw-private'`, or `'raw-seed'`.
21512285
* `cipher` {string} If specified, the private key will be encrypted with
21522286
the given `cipher` and `passphrase` using PKCS#5 v2.0 password based
2153-
encryption.
2154-
* `passphrase` {string | Buffer} The passphrase to use for encryption, see
2155-
`cipher`.
2287+
encryption. Ignored when `format` is `'jwk'`, `'raw-private'`, or
2288+
`'raw-seed'`.
2289+
* `passphrase` {string | Buffer} The passphrase to use for encryption.
2290+
Required when `cipher` is specified.
21562291

21572292
The result type depends on the selected encoding format, when PEM the
21582293
result is a string, when DER it will be a buffer containing the data
2159-
encoded as DER, when [JWK][] it will be an object.
2160-
2161-
When [JWK][] encoding format was selected, all other encoding options are
2162-
ignored.
2294+
encoded as DER, when [JWK][] it will be an object. Raw formats return a
2295+
{Buffer} containing the raw key material.
21632296

2164-
PKCS#1, SEC1, and PKCS#8 type keys can be encrypted by using a combination of
2165-
the `cipher` and `format` options. The PKCS#8 `type` can be used with any
2166-
`format` to encrypt any key algorithm (RSA, EC, or DH) by specifying a
2167-
`cipher`. PKCS#1 and SEC1 can only be encrypted by specifying a `cipher`
2168-
when the PEM `format` is used. For maximum compatibility, use PKCS#8 for
2169-
encrypted private keys. Since PKCS#8 defines its own
2170-
encryption mechanism, PEM-level encryption is not supported when encrypting
2171-
a PKCS#8 key. See [RFC 5208][] for PKCS#8 encryption and [RFC 1421][] for
2172-
PKCS#1 and SEC1 encryption.
2297+
Private keys can be encrypted by specifying a `cipher` and `passphrase`.
2298+
The PKCS#8 `type` supports encryption with both PEM and DER `format` for any
2299+
key algorithm. PKCS#1 and SEC1 can only be encrypted when the PEM `format` is
2300+
used. For maximum compatibility, use PKCS#8 for encrypted private keys. Since
2301+
PKCS#8 defines its own encryption mechanism, PEM-level encryption is not
2302+
supported when encrypting a PKCS#8 key. See [RFC 5208][] for PKCS#8 encryption
2303+
and [RFC 1421][] for PKCS#1 and SEC1 encryption.
21732304

21742305
### `keyObject.symmetricKeySize`
21752306

@@ -3634,6 +3765,10 @@ input.on('readable', () => {
36343765
<!-- YAML
36353766
added: v11.6.0
36363767
changes:
3768+
- version: REPLACEME
3769+
pr-url: https://github.com/nodejs/node/pull/62240
3770+
description: Added support for `'raw-private'` and `'raw-seed'`
3771+
formats.
36373772
- version: v24.6.0
36383773
pr-url: https://github.com/nodejs/node/pull/59259
36393774
description: Add support for ML-DSA keys.
@@ -3651,12 +3786,17 @@ changes:
36513786
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView}
36523787
* `key` {string|ArrayBuffer|Buffer|TypedArray|DataView|Object} The key
36533788
material, either in PEM, DER, or JWK format.
3654-
* `format` {string} Must be `'pem'`, `'der'`, or '`'jwk'`.
3655-
**Default:** `'pem'`.
3789+
* `format` {string} Must be `'pem'`, `'der'`, `'jwk'`, `'raw-private'`,
3790+
or `'raw-seed'`. **Default:** `'pem'`.
36563791
* `type` {string} Must be `'pkcs1'`, `'pkcs8'` or `'sec1'`. This option is
36573792
required only if the `format` is `'der'` and ignored otherwise.
36583793
* `passphrase` {string | Buffer} The passphrase to use for decryption.
36593794
* `encoding` {string} The string encoding to use when `key` is a string.
3795+
* `asymmetricKeyType` {string} Required when `format` is `'raw-private'`
3796+
or `'raw-seed'` and ignored otherwise.
3797+
Must be a [supported key type][asymmetric key types].
3798+
* `namedCurve` {string} Name of the curve to use. Required when
3799+
`asymmetricKeyType` is `'ec'` and ignored otherwise.
36603800
* Returns: {KeyObject}
36613801

36623802
<!--lint enable maximum-line-length remark-lint-->
@@ -3673,6 +3813,9 @@ of the passphrase is limited to 1024 bytes.
36733813
<!-- YAML
36743814
added: v11.6.0
36753815
changes:
3816+
- version: REPLACEME
3817+
pr-url: https://github.com/nodejs/node/pull/62240
3818+
description: Added support for `'raw-public'` format.
36763819
- version: v24.6.0
36773820
pr-url: https://github.com/nodejs/node/pull/59259
36783821
description: Add support for ML-DSA keys.
@@ -3697,11 +3840,16 @@ changes:
36973840
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView}
36983841
* `key` {string|ArrayBuffer|Buffer|TypedArray|DataView|Object} The key
36993842
material, either in PEM, DER, or JWK format.
3700-
* `format` {string} Must be `'pem'`, `'der'`, or `'jwk'`.
3843+
* `format` {string} Must be `'pem'`, `'der'`, `'jwk'`, or `'raw-public'`.
37013844
**Default:** `'pem'`.
37023845
* `type` {string} Must be `'pkcs1'` or `'spki'`. This option is
37033846
required only if the `format` is `'der'` and ignored otherwise.
37043847
* `encoding` {string} The string encoding to use when `key` is a string.
3848+
* `asymmetricKeyType` {string} Required when `format` is `'raw-public'`
3849+
and ignored otherwise.
3850+
Must be a [supported key type][asymmetric key types].
3851+
* `namedCurve` {string} Name of the curve to use. Required when
3852+
`asymmetricKeyType` is `'ec'` and ignored otherwise.
37053853
* Returns: {KeyObject}
37063854

37073855
<!--lint enable maximum-line-length remark-lint-->

0 commit comments

Comments
 (0)