0
# Key Management
1
2
Comprehensive SSH key parsing, generation, and management utilities supporting RSA, ECDSA, and Ed25519 keys with multiple formats and encryption options.
3
4
## Capabilities
5
6
### Key Parsing
7
8
Parse SSH and OpenSSH private and public keys from various formats.
9
10
```javascript { .api }
11
/**
12
* Parse SSH/OpenSSH private or public key
13
* Supports multiple key formats: OpenSSH, PEM, PKCS#1, PKCS#8
14
* @param keyData - Key data as string or Buffer
15
* @param passphrase - Optional passphrase for encrypted keys
16
* @returns Parsed key object or Error if parsing fails
17
*/
18
function parseKey(keyData: string | Buffer, passphrase?: string): ParsedKey | Error;
19
```
20
21
**Key Parsing Examples:**
22
23
```javascript
24
const { utils } = require('ssh2');
25
const fs = require('fs');
26
27
// Parse OpenSSH private key
28
const privateKey = fs.readFileSync('/path/to/id_rsa', 'utf8');
29
const parsedPrivate = utils.parseKey(privateKey, 'passphrase');
30
31
if (parsedPrivate instanceof Error) {
32
console.error('Failed to parse private key:', parsedPrivate.message);
33
} else {
34
console.log('Private key type:', parsedPrivate.type);
35
console.log('Private key comment:', parsedPrivate.comment);
36
}
37
38
// Parse OpenSSH public key
39
const publicKey = fs.readFileSync('/path/to/id_rsa.pub', 'utf8');
40
const parsedPublic = utils.parseKey(publicKey);
41
42
if (parsedPublic instanceof Error) {
43
console.error('Failed to parse public key:', parsedPublic.message);
44
} else {
45
console.log('Public key type:', parsedPublic.type);
46
console.log('Public key fingerprint:', parsedPublic.getPublicSSH().toString('base64'));
47
}
48
49
// Parse PEM format key
50
const pemKey = fs.readFileSync('/path/to/key.pem', 'utf8');
51
const parsedPem = utils.parseKey(pemKey);
52
53
// Parse encrypted key
54
const encryptedKey = fs.readFileSync('/path/to/encrypted_key', 'utf8');
55
const parsedEncrypted = utils.parseKey(encryptedKey, 'my-secret-passphrase');
56
```
57
58
### Key Generation
59
60
Generate new SSH key pairs with various algorithms and options.
61
62
```javascript { .api }
63
/**
64
* Generate SSH key pair asynchronously
65
* @param keyType - Key type: 'rsa', 'ecdsa', or 'ed25519'
66
* @param options - Key generation options
67
* @param callback - Callback receiving generated key pair
68
*/
69
function generateKeyPair(keyType: string, options: KeyGenOptions, callback: KeyGenCallback): void;
70
function generateKeyPair(keyType: string, callback: KeyGenCallback): void;
71
72
/**
73
* Generate SSH key pair synchronously
74
* @param keyType - Key type: 'rsa', 'ecdsa', or 'ed25519'
75
* @param options - Key generation options
76
* @returns Generated key pair
77
*/
78
function generateKeyPairSync(keyType: string, options?: KeyGenOptions): KeyPair;
79
80
interface KeyGenOptions {
81
bits?: number; // Key size (RSA: 2048-4096, ECDSA: 256/384/521)
82
comment?: string; // Key comment
83
format?: string; // Output format ('new' for OpenSSH, 'old' for PEM)
84
passphrase?: string; // Private key encryption passphrase
85
cipher?: string; // Encryption cipher for passphrase protection
86
rounds?: number; // KDF rounds for passphrase (default: 16)
87
}
88
89
interface KeyPair {
90
private: string; // Private key in requested format
91
public: string; // Public key in OpenSSH format
92
}
93
94
type KeyGenCallback = (err: Error | null, keyPair?: KeyPair) => void;
95
```
96
97
**Key Generation Examples:**
98
99
```javascript
100
const { generateKeyPair, generateKeyPairSync } = require('ssh2').utils;
101
102
// Generate RSA key pair asynchronously
103
generateKeyPair('rsa', {
104
bits: 2048,
105
comment: 'user@hostname',
106
format: 'new',
107
passphrase: 'secure-passphrase'
108
}, (err, keyPair) => {
109
if (err) {
110
console.error('Key generation failed:', err.message);
111
return;
112
}
113
114
console.log('Private key:');
115
console.log(keyPair.private);
116
console.log('\nPublic key:');
117
console.log(keyPair.public);
118
119
// Save keys to files
120
require('fs').writeFileSync('id_rsa', keyPair.private, { mode: 0o600 });
121
require('fs').writeFileSync('id_rsa.pub', keyPair.public);
122
});
123
124
// Generate ECDSA key pair synchronously
125
try {
126
const ecdsaKeys = generateKeyPairSync('ecdsa', {
127
bits: 256,
128
comment: 'ECDSA key for production'
129
});
130
131
console.log('ECDSA key generated successfully');
132
console.log('Public key:', ecdsaKeys.public);
133
} catch (err) {
134
console.error('ECDSA generation failed:', err.message);
135
}
136
137
// Generate Ed25519 key pair (most secure)
138
generateKeyPair('ed25519', {
139
comment: 'Ed25519 key for SSH access',
140
format: 'new'
141
}, (err, keyPair) => {
142
if (err) throw err;
143
144
console.log('Ed25519 key pair generated');
145
146
// Ed25519 keys are always the same size, no bits option needed
147
console.log('Private key length:', keyPair.private.length);
148
console.log('Public key:', keyPair.public);
149
});
150
151
// Generate encrypted RSA key with custom cipher
152
generateKeyPair('rsa', {
153
bits: 4096,
154
comment: 'High-security RSA key',
155
passphrase: 'very-secure-password',
156
cipher: 'aes256-ctr',
157
rounds: 32
158
}, (err, keyPair) => {
159
if (err) throw err;
160
161
console.log('Encrypted 4096-bit RSA key generated');
162
});
163
```
164
165
### ParsedKey Object
166
167
The parsed key object provides methods for key operations and format conversion.
168
169
```javascript { .api }
170
interface ParsedKey {
171
// Key properties
172
type: string; // Key type: 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ssh-ed25519', etc.
173
comment?: string; // Key comment (if present)
174
public: Buffer; // Public key data
175
private?: Buffer; // Private key data (if available)
176
177
// Key format conversion methods
178
getPrivatePEM(): string;
179
getPublicPEM(): string;
180
getPublicSSH(): Buffer;
181
182
// Cryptographic operations
183
sign(data: Buffer, hashAlgo?: string): Buffer | Error;
184
verify(data: Buffer, signature: Buffer, hashAlgo?: string): boolean | Error;
185
186
// Key equality checking
187
equals(otherKey: ParsedKey): boolean;
188
}
189
```
190
191
**ParsedKey Usage Examples:**
192
193
```javascript
194
const { utils } = require('ssh2');
195
const fs = require('fs');
196
197
// Load and parse a private key
198
const keyData = fs.readFileSync('id_rsa', 'utf8');
199
const parsedKey = utils.parseKey(keyData, 'passphrase');
200
201
if (parsedKey instanceof Error) {
202
console.error('Key parsing failed:', parsedKey.message);
203
} else {
204
console.log('Key type:', parsedKey.type);
205
console.log('Has private key:', !!parsedKey.private);
206
console.log('Comment:', parsedKey.comment);
207
208
// Convert to different formats
209
console.log('\nPEM format:');
210
console.log(parsedKey.getPrivatePEM());
211
212
console.log('\nOpenSSH public key format:');
213
console.log(parsedKey.getPublicSSH().toString());
214
215
// Sign data
216
const data = Buffer.from('Hello, SSH world!');
217
const signature = parsedKey.sign(data);
218
219
if (signature instanceof Error) {
220
console.error('Signing failed:', signature.message);
221
} else {
222
console.log('Signature:', signature.toString('base64'));
223
224
// Verify signature
225
const verified = parsedKey.verify(data, signature);
226
console.log('Signature verified:', verified);
227
}
228
229
// Compare keys
230
const publicKeyData = fs.readFileSync('id_rsa.pub', 'utf8');
231
const publicKey = utils.parseKey(publicKeyData);
232
233
if (publicKey instanceof Error) {
234
console.error('Public key parsing failed');
235
} else {
236
console.log('Keys match:', parsedKey.equals(publicKey));
237
}
238
}
239
```
240
241
### Key Format Support
242
243
Support for multiple SSH and cryptographic key formats.
244
245
#### Supported Key Types
246
247
```javascript { .api }
248
// RSA keys
249
type: 'ssh-rsa'
250
251
// ECDSA keys
252
type: 'ecdsa-sha2-nistp256' // NIST P-256
253
type: 'ecdsa-sha2-nistp384' // NIST P-384
254
type: 'ecdsa-sha2-nistp521' // NIST P-521
255
256
// Ed25519 keys
257
type: 'ssh-ed25519'
258
259
// Legacy DSA keys (deprecated)
260
type: 'ssh-dss'
261
```
262
263
#### Supported Input Formats
264
265
- **OpenSSH**: Native OpenSSH private/public key format
266
- **PEM**: Privacy-Enhanced Mail format (PKCS#1, PKCS#8)
267
- **RFC4716**: SSH.com public key format
268
- **Legacy**: Various legacy formats
269
270
```javascript
271
// Examples of supported formats
272
273
// OpenSSH private key
274
const opensshPrivate = `-----BEGIN OPENSSH PRIVATE KEY-----
275
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn
276
...
277
-----END OPENSSH PRIVATE KEY-----`;
278
279
// OpenSSH public key
280
const opensshPublic = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC... user@hostname`;
281
282
// PEM private key
283
const pemPrivate = `-----BEGIN RSA PRIVATE KEY-----
284
MIIEpAIBAAKCAQEA5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j
285
...
286
-----END RSA PRIVATE KEY-----`;
287
288
// All can be parsed with utils.parseKey()
289
const key1 = utils.parseKey(opensshPrivate);
290
const key2 = utils.parseKey(opensshPublic);
291
const key3 = utils.parseKey(pemPrivate);
292
```
293
294
### Advanced Key Operations
295
296
Advanced operations for key management and cryptographic functions.
297
298
```javascript { .api }
299
/**
300
* Check if object is a parsed key
301
* @param obj - Object to check
302
* @returns true if object is ParsedKey
303
*/
304
function isParsedKey(obj: any): obj is ParsedKey;
305
306
/**
307
* Generate key fingerprint
308
* @param key - Parsed key object
309
* @param hashAlgo - Hash algorithm ('md5', 'sha1', 'sha256')
310
* @returns Key fingerprint string
311
*/
312
function generateFingerprint(key: ParsedKey, hashAlgo?: string): string;
313
```
314
315
**Advanced Operations Examples:**
316
317
```javascript
318
const { utils } = require('ssh2');
319
const crypto = require('crypto');
320
321
// Key validation and fingerprinting
322
function analyzeKey(keyPath, passphrase) {
323
const keyData = require('fs').readFileSync(keyPath, 'utf8');
324
const key = utils.parseKey(keyData, passphrase);
325
326
if (key instanceof Error) {
327
console.error('Invalid key:', key.message);
328
return;
329
}
330
331
console.log('Key Analysis:');
332
console.log(' Type:', key.type);
333
console.log(' Comment:', key.comment || '(no comment)');
334
console.log(' Has private key:', !!key.private);
335
336
// Generate fingerprints
337
const publicSSH = key.getPublicSSH();
338
const md5Hash = crypto.createHash('md5').update(publicSSH).digest('hex');
339
const sha256Hash = crypto.createHash('sha256').update(publicSSH).digest('base64');
340
341
console.log(' MD5 fingerprint:', md5Hash.replace(/(.{2})/g, '$1:').slice(0, -1));
342
console.log(' SHA256 fingerprint:', sha256Hash);
343
344
// Key strength analysis
345
if (key.type === 'ssh-rsa') {
346
const keySize = key.public.length * 8; // Rough estimate
347
console.log(' Estimated key size:', keySize, 'bits');
348
349
if (keySize < 2048) {
350
console.log(' WARNING: RSA key size may be too small for security');
351
}
352
}
353
354
return key;
355
}
356
357
// Key conversion utility
358
function convertKeyFormat(inputPath, outputPath, targetFormat, passphrase) {
359
const keyData = require('fs').readFileSync(inputPath, 'utf8');
360
const key = utils.parseKey(keyData, passphrase);
361
362
if (key instanceof Error) {
363
throw new Error(`Failed to parse key: ${key.message}`);
364
}
365
366
let output;
367
switch (targetFormat) {
368
case 'openssh-private':
369
output = key.getPrivatePEM(); // Note: This gets PEM, OpenSSH format not directly available
370
break;
371
case 'openssh-public':
372
output = key.getPublicSSH().toString();
373
break;
374
case 'pem-private':
375
output = key.getPrivatePEM();
376
break;
377
case 'pem-public':
378
output = key.getPublicPEM();
379
break;
380
default:
381
throw new Error('Unsupported target format');
382
}
383
384
require('fs').writeFileSync(outputPath, output, { mode: 0o600 });
385
console.log(`Converted key saved to ${outputPath}`);
386
}
387
388
// Usage examples
389
analyzeKey('id_rsa', 'passphrase');
390
convertKeyFormat('id_rsa', 'id_rsa.pem', 'pem-private', 'passphrase');
391
```
392
393
### Key Validation and Security
394
395
Utilities for validating key security and best practices.
396
397
```javascript
398
// Key security analyzer
399
class KeySecurityAnalyzer {
400
static analyzeKey(key) {
401
const analysis = {
402
type: key.type,
403
secure: true,
404
warnings: [],
405
recommendations: []
406
};
407
408
switch (key.type) {
409
case 'ssh-rsa':
410
// RSA key analysis
411
const rsaBits = this.estimateRSAKeySize(key);
412
analysis.keySize = rsaBits;
413
414
if (rsaBits < 2048) {
415
analysis.secure = false;
416
analysis.warnings.push('RSA key size is less than 2048 bits');
417
analysis.recommendations.push('Generate a new RSA key with at least 2048 bits');
418
} else if (rsaBits < 3072) {
419
analysis.warnings.push('RSA key size is less than 3072 bits (recommended for long-term use)');
420
}
421
break;
422
423
case 'ecdsa-sha2-nistp256':
424
analysis.keySize = 256;
425
analysis.recommendations.push('Consider Ed25519 for better performance and security');
426
break;
427
428
case 'ecdsa-sha2-nistp384':
429
analysis.keySize = 384;
430
break;
431
432
case 'ecdsa-sha2-nistp521':
433
analysis.keySize = 521;
434
break;
435
436
case 'ssh-ed25519':
437
analysis.keySize = 256;
438
analysis.recommendations.push('Ed25519 is currently the most secure and efficient option');
439
break;
440
441
case 'ssh-dss':
442
analysis.secure = false;
443
analysis.warnings.push('DSA keys are deprecated and insecure');
444
analysis.recommendations.push('Replace with RSA, ECDSA, or Ed25519 key');
445
break;
446
447
default:
448
analysis.warnings.push('Unknown or unsupported key type');
449
}
450
451
return analysis;
452
}
453
454
static estimateRSAKeySize(key) {
455
// This is a rough estimation - actual implementation would need to parse the key structure
456
const publicKeyLen = key.public.length;
457
return Math.floor(publicKeyLen * 4); // Rough approximation
458
}
459
460
static checkKeyAge(keyPath) {
461
const stats = require('fs').statSync(keyPath);
462
const ageInDays = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60 * 24);
463
464
if (ageInDays > 365 * 2) {
465
return {
466
age: ageInDays,
467
warning: 'Key is older than 2 years, consider rotation'
468
};
469
}
470
471
return { age: ageInDays };
472
}
473
}
474
475
// Usage
476
const key = utils.parseKey(keyData);
477
if (!(key instanceof Error)) {
478
const analysis = KeySecurityAnalyzer.analyzeKey(key);
479
console.log('Security Analysis:', analysis);
480
481
const ageInfo = KeySecurityAnalyzer.checkKeyAge('id_rsa');
482
console.log('Key Age:', Math.floor(ageInfo.age), 'days');
483
if (ageInfo.warning) {
484
console.log('Age Warning:', ageInfo.warning);
485
}
486
}
487
```
488
489
## Cipher Support
490
491
Available ciphers for key encryption when generating keys with passphrases.
492
493
```javascript { .api }
494
// Supported ciphers for key encryption
495
const SUPPORTED_CIPHERS = [
496
'aes128-cbc',
497
'aes128-ctr',
498
'aes192-cbc',
499
'aes192-ctr',
500
'aes256-cbc',
501
'aes256-ctr',
502
'aes128-gcm@openssh.com',
503
'aes256-gcm@openssh.com'
504
];
505
506
// Generate key with specific cipher
507
generateKeyPair('rsa', {
508
bits: 2048,
509
passphrase: 'secure-password',
510
cipher: 'aes256-ctr',
511
rounds: 32
512
}, (err, keyPair) => {
513
if (err) throw err;
514
console.log('Key generated with AES-256-CTR encryption');
515
});
516
```
517
518
## Error Handling
519
520
Common errors and how to handle them when working with keys.
521
522
```javascript
523
const { utils } = require('ssh2');
524
525
function safeParseKey(keyData, passphrase) {
526
try {
527
const key = utils.parseKey(keyData, passphrase);
528
529
if (key instanceof Error) {
530
switch (key.message) {
531
case 'Invalid key format':
532
console.error('The key file is not in a recognized format');
533
break;
534
case 'Invalid passphrase':
535
console.error('The passphrase provided is incorrect');
536
break;
537
case 'Encrypted key without passphrase':
538
console.error('This key is encrypted and requires a passphrase');
539
break;
540
default:
541
console.error('Key parsing error:', key.message);
542
}
543
return null;
544
}
545
546
return key;
547
} catch (err) {
548
console.error('Unexpected error parsing key:', err.message);
549
return null;
550
}
551
}
552
553
function safeGenerateKey(type, options, callback) {
554
generateKeyPair(type, options, (err, keyPair) => {
555
if (err) {
556
if (err.message.includes('Invalid key type')) {
557
console.error('Unsupported key type:', type);
558
} else if (err.message.includes('Invalid bits')) {
559
console.error('Invalid key size for', type);
560
} else {
561
console.error('Key generation failed:', err.message);
562
}
563
callback(err);
564
} else {
565
callback(null, keyPair);
566
}
567
});
568
}
569
```
570
571
## Best Practices
572
573
### Key Generation Recommendations
574
575
```javascript
576
// Recommended key generation settings
577
const RECOMMENDED_SETTINGS = {
578
rsa: {
579
bits: 3072, // Good balance of security and performance
580
cipher: 'aes256-ctr',
581
rounds: 16
582
},
583
ecdsa: {
584
bits: 384, // NIST P-384 provides good security
585
cipher: 'aes256-ctr',
586
rounds: 16
587
},
588
ed25519: {
589
// Ed25519 has fixed parameters, only cipher and rounds apply
590
cipher: 'aes256-ctr',
591
rounds: 16
592
}
593
};
594
595
function generateSecureKey(type, comment, passphrase) {
596
const options = {
597
...RECOMMENDED_SETTINGS[type],
598
comment: comment || `${type.toUpperCase()} key generated ${new Date().toISOString()}`,
599
format: 'new',
600
passphrase: passphrase
601
};
602
603
return new Promise((resolve, reject) => {
604
generateKeyPair(type, options, (err, keyPair) => {
605
if (err) reject(err);
606
else resolve(keyPair);
607
});
608
});
609
}
610
611
// Usage
612
generateSecureKey('ed25519', 'Production server key', 'strong-passphrase')
613
.then(keyPair => {
614
console.log('Secure Ed25519 key generated');
615
// Save keys securely
616
})
617
.catch(err => {
618
console.error('Key generation failed:', err);
619
});
620
```
621
622
### Key Storage Security
623
624
```javascript
625
const fs = require('fs');
626
const path = require('path');
627
628
class SecureKeyStorage {
629
static saveKeyPair(keyPair, baseName, directory = '.ssh') {
630
const privateKeyPath = path.join(directory, baseName);
631
const publicKeyPath = `${privateKeyPath}.pub`;
632
633
// Ensure directory exists
634
if (!fs.existsSync(directory)) {
635
fs.mkdirSync(directory, { mode: 0o700 });
636
}
637
638
// Save private key with restrictive permissions
639
fs.writeFileSync(privateKeyPath, keyPair.private, { mode: 0o600 });
640
641
// Save public key with standard permissions
642
fs.writeFileSync(publicKeyPath, keyPair.public, { mode: 0o644 });
643
644
console.log(`Key pair saved:`);
645
console.log(` Private: ${privateKeyPath} (600)`);
646
console.log(` Public: ${publicKeyPath} (644)`);
647
648
return { privateKeyPath, publicKeyPath };
649
}
650
651
static loadKeyPair(baseName, directory = '.ssh', passphrase) {
652
const privateKeyPath = path.join(directory, baseName);
653
const publicKeyPath = `${privateKeyPath}.pub`;
654
655
const privateKeyData = fs.readFileSync(privateKeyPath, 'utf8');
656
const publicKeyData = fs.readFileSync(publicKeyPath, 'utf8');
657
658
const privateKey = utils.parseKey(privateKeyData, passphrase);
659
const publicKey = utils.parseKey(publicKeyData);
660
661
if (privateKey instanceof Error) {
662
throw new Error(`Failed to load private key: ${privateKey.message}`);
663
}
664
665
if (publicKey instanceof Error) {
666
throw new Error(`Failed to load public key: ${publicKey.message}`);
667
}
668
669
return { privateKey, publicKey };
670
}
671
}
672
```