CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ssh2

SSH2 client and server modules written in pure JavaScript for node.js

Pending
Overview
Eval results
Files

key-management.mddocs/

Key Management

Comprehensive SSH key parsing, generation, and management utilities supporting RSA, ECDSA, and Ed25519 keys with multiple formats and encryption options.

Capabilities

Key Parsing

Parse SSH and OpenSSH private and public keys from various formats.

/**
 * Parse SSH/OpenSSH private or public key
 * Supports multiple key formats: OpenSSH, PEM, PKCS#1, PKCS#8
 * @param keyData - Key data as string or Buffer
 * @param passphrase - Optional passphrase for encrypted keys
 * @returns Parsed key object or Error if parsing fails
 */
function parseKey(keyData: string | Buffer, passphrase?: string): ParsedKey | Error;

Key Parsing Examples:

const { utils } = require('ssh2');
const fs = require('fs');

// Parse OpenSSH private key
const privateKey = fs.readFileSync('/path/to/id_rsa', 'utf8');
const parsedPrivate = utils.parseKey(privateKey, 'passphrase');

if (parsedPrivate instanceof Error) {
  console.error('Failed to parse private key:', parsedPrivate.message);
} else {
  console.log('Private key type:', parsedPrivate.type);
  console.log('Private key comment:', parsedPrivate.comment);
}

// Parse OpenSSH public key
const publicKey = fs.readFileSync('/path/to/id_rsa.pub', 'utf8');
const parsedPublic = utils.parseKey(publicKey);

if (parsedPublic instanceof Error) {
  console.error('Failed to parse public key:', parsedPublic.message);
} else {
  console.log('Public key type:', parsedPublic.type);
  console.log('Public key fingerprint:', parsedPublic.getPublicSSH().toString('base64'));
}

// Parse PEM format key
const pemKey = fs.readFileSync('/path/to/key.pem', 'utf8');
const parsedPem = utils.parseKey(pemKey);

// Parse encrypted key
const encryptedKey = fs.readFileSync('/path/to/encrypted_key', 'utf8');
const parsedEncrypted = utils.parseKey(encryptedKey, 'my-secret-passphrase');

Key Generation

Generate new SSH key pairs with various algorithms and options.

/**
 * Generate SSH key pair asynchronously
 * @param keyType - Key type: 'rsa', 'ecdsa', or 'ed25519'
 * @param options - Key generation options
 * @param callback - Callback receiving generated key pair
 */
function generateKeyPair(keyType: string, options: KeyGenOptions, callback: KeyGenCallback): void;
function generateKeyPair(keyType: string, callback: KeyGenCallback): void;

/**
 * Generate SSH key pair synchronously
 * @param keyType - Key type: 'rsa', 'ecdsa', or 'ed25519' 
 * @param options - Key generation options
 * @returns Generated key pair
 */  
function generateKeyPairSync(keyType: string, options?: KeyGenOptions): KeyPair;

interface KeyGenOptions {
  bits?: number;          // Key size (RSA: 2048-4096, ECDSA: 256/384/521)
  comment?: string;       // Key comment
  format?: string;        // Output format ('new' for OpenSSH, 'old' for PEM)
  passphrase?: string;    // Private key encryption passphrase
  cipher?: string;        // Encryption cipher for passphrase protection
  rounds?: number;        // KDF rounds for passphrase (default: 16)
}

interface KeyPair {
  private: string;        // Private key in requested format
  public: string;         // Public key in OpenSSH format
}

type KeyGenCallback = (err: Error | null, keyPair?: KeyPair) => void;

Key Generation Examples:

const { generateKeyPair, generateKeyPairSync } = require('ssh2').utils;

// Generate RSA key pair asynchronously
generateKeyPair('rsa', {
  bits: 2048,
  comment: 'user@hostname',
  format: 'new',
  passphrase: 'secure-passphrase'
}, (err, keyPair) => {
  if (err) {
    console.error('Key generation failed:', err.message);
    return;
  }
  
  console.log('Private key:');
  console.log(keyPair.private);
  console.log('\nPublic key:');
  console.log(keyPair.public);
  
  // Save keys to files
  require('fs').writeFileSync('id_rsa', keyPair.private, { mode: 0o600 });
  require('fs').writeFileSync('id_rsa.pub', keyPair.public);
});

// Generate ECDSA key pair synchronously
try {
  const ecdsaKeys = generateKeyPairSync('ecdsa', {
    bits: 256,
    comment: 'ECDSA key for production'
  });
  
  console.log('ECDSA key generated successfully');
  console.log('Public key:', ecdsaKeys.public);
} catch (err) {
  console.error('ECDSA generation failed:', err.message);
}

// Generate Ed25519 key pair (most secure)
generateKeyPair('ed25519', {
  comment: 'Ed25519 key for SSH access',
  format: 'new'
}, (err, keyPair) => {
  if (err) throw err;
  
  console.log('Ed25519 key pair generated');
  
  // Ed25519 keys are always the same size, no bits option needed
  console.log('Private key length:', keyPair.private.length);
  console.log('Public key:', keyPair.public);
});

// Generate encrypted RSA key with custom cipher
generateKeyPair('rsa', {
  bits: 4096,
  comment: 'High-security RSA key',
  passphrase: 'very-secure-password',
  cipher: 'aes256-ctr',
  rounds: 32
}, (err, keyPair) => {
  if (err) throw err;
  
  console.log('Encrypted 4096-bit RSA key generated');
});

ParsedKey Object

The parsed key object provides methods for key operations and format conversion.

interface ParsedKey {
  // Key properties
  type: string;           // Key type: 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ssh-ed25519', etc.
  comment?: string;       // Key comment (if present)
  public: Buffer;         // Public key data
  private?: Buffer;       // Private key data (if available)
  
  // Key format conversion methods
  getPrivatePEM(): string;
  getPublicPEM(): string;  
  getPublicSSH(): Buffer;
  
  // Cryptographic operations
  sign(data: Buffer, hashAlgo?: string): Buffer | Error;
  verify(data: Buffer, signature: Buffer, hashAlgo?: string): boolean | Error;
  
  // Key equality checking
  equals(otherKey: ParsedKey): boolean;
}

ParsedKey Usage Examples:

const { utils } = require('ssh2');
const fs = require('fs');

// Load and parse a private key
const keyData = fs.readFileSync('id_rsa', 'utf8');
const parsedKey = utils.parseKey(keyData, 'passphrase');

if (parsedKey instanceof Error) {
  console.error('Key parsing failed:', parsedKey.message);
} else {
  console.log('Key type:', parsedKey.type);
  console.log('Has private key:', !!parsedKey.private);
  console.log('Comment:', parsedKey.comment);
  
  // Convert to different formats
  console.log('\nPEM format:');
  console.log(parsedKey.getPrivatePEM());
  
  console.log('\nOpenSSH public key format:');
  console.log(parsedKey.getPublicSSH().toString());
  
  // Sign data
  const data = Buffer.from('Hello, SSH world!');
  const signature = parsedKey.sign(data);
  
  if (signature instanceof Error) {
    console.error('Signing failed:', signature.message);
  } else {
    console.log('Signature:', signature.toString('base64'));
    
    // Verify signature
    const verified = parsedKey.verify(data, signature);
    console.log('Signature verified:', verified);
  }
  
  // Compare keys
  const publicKeyData = fs.readFileSync('id_rsa.pub', 'utf8');
  const publicKey = utils.parseKey(publicKeyData);
  
  if (publicKey instanceof Error) {
    console.error('Public key parsing failed');
  } else {
    console.log('Keys match:', parsedKey.equals(publicKey));
  }
}

Key Format Support

Support for multiple SSH and cryptographic key formats.

Supported Key Types

// RSA keys
type: 'ssh-rsa'

// ECDSA keys  
type: 'ecdsa-sha2-nistp256'  // NIST P-256
type: 'ecdsa-sha2-nistp384'  // NIST P-384  
type: 'ecdsa-sha2-nistp521'  // NIST P-521

// Ed25519 keys
type: 'ssh-ed25519'

// Legacy DSA keys (deprecated)
type: 'ssh-dss'

Supported Input Formats

  • OpenSSH: Native OpenSSH private/public key format
  • PEM: Privacy-Enhanced Mail format (PKCS#1, PKCS#8)
  • RFC4716: SSH.com public key format
  • Legacy: Various legacy formats
// Examples of supported formats

// OpenSSH private key
const opensshPrivate = `-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn
...
-----END OPENSSH PRIVATE KEY-----`;

// OpenSSH public key
const opensshPublic = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC... user@hostname`;

// PEM private key
const pemPrivate = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j5j
...
-----END RSA PRIVATE KEY-----`;

// All can be parsed with utils.parseKey()
const key1 = utils.parseKey(opensshPrivate);
const key2 = utils.parseKey(opensshPublic);  
const key3 = utils.parseKey(pemPrivate);

Advanced Key Operations

Advanced operations for key management and cryptographic functions.

/**
 * Check if object is a parsed key
 * @param obj - Object to check
 * @returns true if object is ParsedKey
 */
function isParsedKey(obj: any): obj is ParsedKey;

/**
 * Generate key fingerprint
 * @param key - Parsed key object
 * @param hashAlgo - Hash algorithm ('md5', 'sha1', 'sha256')
 * @returns Key fingerprint string
 */
function generateFingerprint(key: ParsedKey, hashAlgo?: string): string;

Advanced Operations Examples:

const { utils } = require('ssh2');
const crypto = require('crypto');

// Key validation and fingerprinting
function analyzeKey(keyPath, passphrase) {
  const keyData = require('fs').readFileSync(keyPath, 'utf8');
  const key = utils.parseKey(keyData, passphrase);
  
  if (key instanceof Error) {
    console.error('Invalid key:', key.message);
    return;
  }
  
  console.log('Key Analysis:');
  console.log('  Type:', key.type);
  console.log('  Comment:', key.comment || '(no comment)');
  console.log('  Has private key:', !!key.private);
  
  // Generate fingerprints
  const publicSSH = key.getPublicSSH();
  const md5Hash = crypto.createHash('md5').update(publicSSH).digest('hex');
  const sha256Hash = crypto.createHash('sha256').update(publicSSH).digest('base64');
  
  console.log('  MD5 fingerprint:', md5Hash.replace(/(.{2})/g, '$1:').slice(0, -1));
  console.log('  SHA256 fingerprint:', sha256Hash);
  
  // Key strength analysis
  if (key.type === 'ssh-rsa') {
    const keySize = key.public.length * 8; // Rough estimate
    console.log('  Estimated key size:', keySize, 'bits');
    
    if (keySize < 2048) {
      console.log('  WARNING: RSA key size may be too small for security');
    }
  }
  
  return key;
}

// Key conversion utility
function convertKeyFormat(inputPath, outputPath, targetFormat, passphrase) {
  const keyData = require('fs').readFileSync(inputPath, 'utf8');
  const key = utils.parseKey(keyData, passphrase);
  
  if (key instanceof Error) {
    throw new Error(`Failed to parse key: ${key.message}`);
  }
  
  let output;
  switch (targetFormat) {
    case 'openssh-private':
      output = key.getPrivatePEM(); // Note: This gets PEM, OpenSSH format not directly available
      break;
    case 'openssh-public':
      output = key.getPublicSSH().toString();
      break;
    case 'pem-private':
      output = key.getPrivatePEM();
      break;
    case 'pem-public':
      output = key.getPublicPEM();
      break;
    default:
      throw new Error('Unsupported target format');
  }
  
  require('fs').writeFileSync(outputPath, output, { mode: 0o600 });
  console.log(`Converted key saved to ${outputPath}`);
}

// Usage examples
analyzeKey('id_rsa', 'passphrase');
convertKeyFormat('id_rsa', 'id_rsa.pem', 'pem-private', 'passphrase');

Key Validation and Security

Utilities for validating key security and best practices.

// Key security analyzer
class KeySecurityAnalyzer {
  static analyzeKey(key) {
    const analysis = {
      type: key.type,
      secure: true,
      warnings: [],
      recommendations: []
    };
    
    switch (key.type) {
      case 'ssh-rsa':
        // RSA key analysis
        const rsaBits = this.estimateRSAKeySize(key);
        analysis.keySize = rsaBits;
        
        if (rsaBits < 2048) {
          analysis.secure = false;
          analysis.warnings.push('RSA key size is less than 2048 bits');
          analysis.recommendations.push('Generate a new RSA key with at least 2048 bits');
        } else if (rsaBits < 3072) {
          analysis.warnings.push('RSA key size is less than 3072 bits (recommended for long-term use)');
        }
        break;
        
      case 'ecdsa-sha2-nistp256':
        analysis.keySize = 256;
        analysis.recommendations.push('Consider Ed25519 for better performance and security');
        break;
        
      case 'ecdsa-sha2-nistp384':
        analysis.keySize = 384;
        break;
        
      case 'ecdsa-sha2-nistp521':
        analysis.keySize = 521;
        break;
        
      case 'ssh-ed25519':
        analysis.keySize = 256;
        analysis.recommendations.push('Ed25519 is currently the most secure and efficient option');
        break;
        
      case 'ssh-dss':
        analysis.secure = false;
        analysis.warnings.push('DSA keys are deprecated and insecure');
        analysis.recommendations.push('Replace with RSA, ECDSA, or Ed25519 key');
        break;
        
      default:
        analysis.warnings.push('Unknown or unsupported key type');
    }
    
    return analysis;
  }
  
  static estimateRSAKeySize(key) {
    // This is a rough estimation - actual implementation would need to parse the key structure
    const publicKeyLen = key.public.length;
    return Math.floor(publicKeyLen * 4); // Rough approximation
  }
  
  static checkKeyAge(keyPath) {
    const stats = require('fs').statSync(keyPath);
    const ageInDays = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60 * 24);
    
    if (ageInDays > 365 * 2) {
      return {
        age: ageInDays,
        warning: 'Key is older than 2 years, consider rotation'
      };
    }
    
    return { age: ageInDays };
  }
}

// Usage
const key = utils.parseKey(keyData);
if (!(key instanceof Error)) {
  const analysis = KeySecurityAnalyzer.analyzeKey(key);
  console.log('Security Analysis:', analysis);
  
  const ageInfo = KeySecurityAnalyzer.checkKeyAge('id_rsa');
  console.log('Key Age:', Math.floor(ageInfo.age), 'days');
  if (ageInfo.warning) {
    console.log('Age Warning:', ageInfo.warning);
  }
}

Cipher Support

Available ciphers for key encryption when generating keys with passphrases.

// Supported ciphers for key encryption
const SUPPORTED_CIPHERS = [
  'aes128-cbc',
  'aes128-ctr', 
  'aes192-cbc',
  'aes192-ctr',
  'aes256-cbc',
  'aes256-ctr',
  'aes128-gcm@openssh.com',
  'aes256-gcm@openssh.com'
];

// Generate key with specific cipher
generateKeyPair('rsa', {
  bits: 2048,
  passphrase: 'secure-password',
  cipher: 'aes256-ctr',
  rounds: 32
}, (err, keyPair) => {
  if (err) throw err;
  console.log('Key generated with AES-256-CTR encryption');
});

Error Handling

Common errors and how to handle them when working with keys.

const { utils } = require('ssh2');

function safeParseKey(keyData, passphrase) {
  try {
    const key = utils.parseKey(keyData, passphrase);
    
    if (key instanceof Error) {
      switch (key.message) {
        case 'Invalid key format':
          console.error('The key file is not in a recognized format');
          break;
        case 'Invalid passphrase':
          console.error('The passphrase provided is incorrect');
          break;
        case 'Encrypted key without passphrase':
          console.error('This key is encrypted and requires a passphrase');
          break;
        default:
          console.error('Key parsing error:', key.message);
      }
      return null;
    }
    
    return key;
  } catch (err) {
    console.error('Unexpected error parsing key:', err.message);
    return null;
  }
}

function safeGenerateKey(type, options, callback) {
  generateKeyPair(type, options, (err, keyPair) => {
    if (err) {
      if (err.message.includes('Invalid key type')) {
        console.error('Unsupported key type:', type);
      } else if (err.message.includes('Invalid bits')) {
        console.error('Invalid key size for', type);
      } else {
        console.error('Key generation failed:', err.message);
      }
      callback(err);
    } else {
      callback(null, keyPair);
    }
  });
}

Best Practices

Key Generation Recommendations

// Recommended key generation settings
const RECOMMENDED_SETTINGS = {
  rsa: {
    bits: 3072,         // Good balance of security and performance
    cipher: 'aes256-ctr',
    rounds: 16
  },
  ecdsa: {
    bits: 384,          // NIST P-384 provides good security
    cipher: 'aes256-ctr',
    rounds: 16
  },
  ed25519: {
    // Ed25519 has fixed parameters, only cipher and rounds apply
    cipher: 'aes256-ctr',
    rounds: 16
  }
};

function generateSecureKey(type, comment, passphrase) {
  const options = {
    ...RECOMMENDED_SETTINGS[type],
    comment: comment || `${type.toUpperCase()} key generated ${new Date().toISOString()}`,
    format: 'new',
    passphrase: passphrase
  };
  
  return new Promise((resolve, reject) => {
    generateKeyPair(type, options, (err, keyPair) => {
      if (err) reject(err);
      else resolve(keyPair);
    });
  });
}

// Usage
generateSecureKey('ed25519', 'Production server key', 'strong-passphrase')
  .then(keyPair => {
    console.log('Secure Ed25519 key generated');
    // Save keys securely
  })
  .catch(err => {
    console.error('Key generation failed:', err);
  });

Key Storage Security

const fs = require('fs');
const path = require('path');

class SecureKeyStorage {
  static saveKeyPair(keyPair, baseName, directory = '.ssh') {
    const privateKeyPath = path.join(directory, baseName);
    const publicKeyPath = `${privateKeyPath}.pub`;
    
    // Ensure directory exists
    if (!fs.existsSync(directory)) {
      fs.mkdirSync(directory, { mode: 0o700 });
    }
    
    // Save private key with restrictive permissions
    fs.writeFileSync(privateKeyPath, keyPair.private, { mode: 0o600 });
    
    // Save public key with standard permissions
    fs.writeFileSync(publicKeyPath, keyPair.public, { mode: 0o644 });
    
    console.log(`Key pair saved:`);
    console.log(`  Private: ${privateKeyPath} (600)`);
    console.log(`  Public:  ${publicKeyPath} (644)`);
    
    return { privateKeyPath, publicKeyPath };
  }
  
  static loadKeyPair(baseName, directory = '.ssh', passphrase) {
    const privateKeyPath = path.join(directory, baseName);
    const publicKeyPath = `${privateKeyPath}.pub`;
    
    const privateKeyData = fs.readFileSync(privateKeyPath, 'utf8');
    const publicKeyData = fs.readFileSync(publicKeyPath, 'utf8');
    
    const privateKey = utils.parseKey(privateKeyData, passphrase);
    const publicKey = utils.parseKey(publicKeyData);
    
    if (privateKey instanceof Error) {
      throw new Error(`Failed to load private key: ${privateKey.message}`);
    }
    
    if (publicKey instanceof Error) {
      throw new Error(`Failed to load public key: ${publicKey.message}`);
    }
    
    return { privateKey, publicKey };
  }
}

Install with Tessl CLI

npx tessl i tessl/npm-ssh2

docs

http-tunneling.md

index.md

key-management.md

port-forwarding.md

sftp-operations.md

ssh-agents.md

ssh-client.md

ssh-server.md

tile.json