Skip to content

Import, convert and export SSH keys ​

Use micro509/ssh to read OpenSSH key material into WebCrypto CryptoKeys, convert it to PEM formats, and write OpenSSH keys again. RSA, Ed25519 and ECDSA P-256/P-384/P-521 are supported.

The examples below all use the same Ed25519 demo key. Its public line, private file and SPKI are included so each example runs independently and their fingerprints can be compared. This key is published demo material.

Inspect a public key ​

Pass the contents of a .pub file to importSshPublicKey. The result includes the comment, algorithm, public CryptoKey and OpenSSH-style SHA-256 fingerprint. For an existing file, read its text in your application and replace publicLine.

ts
import { exportSpkiPem } from 'micro509/keys';
import { importSshPublicKey } from 'micro509/ssh';

const publicLine =
  'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEe8pyyvj5Dmkk+3GLdlRTCy23tkJN+qAoA277AqkMTa docs@example.com';
const result = await importSshPublicKey(publicLine);
if (!result.ok) throw new Error(result.error.code);

console.log('Public line:', publicLine);
console.log('Key type:', result.value.keyType);
console.log('Comment:', result.value.comment);
console.log('Fingerprint:', result.value.fingerprint);
console.log('SPKI PEM:\n' + (await exportSpkiPem(result.value.publicKey)));

Save the printed public line as demo.pub. OpenSSH should print the same fingerprint:

bash
ssh-keygen -lf demo.pub -E sha256

For this key, the fingerprint is SHA256:h4TPQn6XDJNXs9cPaExw1sEckQM+SWlkc3GdYQfyRLA. The fingerprint identifies the SSH public-key blob, so converting the key to SPKI and back produces the same fingerprint. Hashing the SPKI bytes directly would hash a different encoding.

Import and convert a private key ​

importOpenSshPrivateKey accepts an unencrypted, single-key OpenSSH private file as text or bytes. Password-protected files return unsupported.

This example imports the demo private key, converts it to PKCS#8, and exports it as an OpenSSH private file again. It also prints the corresponding public line and fingerprint for comparison with the first example.

ts
import { exportPkcs8Pem } from 'micro509/keys';
import {
  exportOpenSshPrivateKey,
  exportSshPublicKey,
  importOpenSshPrivateKey,
} from 'micro509/ssh';

const privateFile = `-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz
c2gtZWQyNTUxOQAAACBHvKcsr4+Q5pJPtxi3ZUUwstt7ZCTfqgKANu+wKpDE2gAA
AJjG4ObKxuDmygAAAAtzc2gtZWQyNTUxOQAAACBHvKcsr4+Q5pJPtxi3ZUUwstt7
ZCTfqgKANu+wKpDE2gAAAECqFGHsHLN5lRCCAPIxD/kHFhY1mlExMyCLZ9zGqUvE
H0e8pyyvj5Dmkk+3GLdlRTCy23tkJN+qAoA277AqkMTaAAAAEGRvY3NAZXhhbXBs
ZS5jb20BAgMEBQ==
-----END OPENSSH PRIVATE KEY-----`;
const result = await importOpenSshPrivateKey(privateFile);
if (!result.ok) throw new Error(result.error.code);
const imported = result.value;

console.log('Fingerprint:', imported.fingerprint);
console.log(
  'Public line:',
  await exportSshPublicKey(imported.publicKey, {
    comment: imported.comment,
  }),
);
console.log(
  'PKCS#8 private key:\n' + (await exportPkcs8Pem(imported.privateKey)),
);
console.log(
  'OpenSSH private key:\n' +
    (await exportOpenSshPrivateKey(imported.privateKey, {
      comment: imported.comment,
    })),
);

The imported private key has sign usage and its public key has verify usage. Both are extractable. Use the Keys guide for PKCS#8, SPKI and JWK conversions, or encrypted PKCS#8 to produce a password-protected private-key container.

OpenSSH private exports include random check integers. Two exported files can differ while holding the same key. Compare their public keys or fingerprints.

Export a public key to OpenSSH ​

Here is the SPKI printed by the first example. Importing it and calling exportSshPublicKey produces the original .pub line. The comment is metadata you supply separately from the key.

ts
import { importSpkiPemOrThrow } from 'micro509/keys';
import { exportSshPublicKey, sshFingerprintSha256 } from 'micro509/ssh';

const spki = `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAR7ynLK+PkOaST7cYt2VFMLLbe2Qk36oCgDbvsCqQxNo=
-----END PUBLIC KEY-----`;
const publicKey = await importSpkiPemOrThrow(spki);
console.log(
  await exportSshPublicKey(publicKey, { comment: 'docs@example.com' }),
);
console.log('Fingerprint:', await sshFingerprintSha256(publicKey));

Export requires an extractable key. exportSshPublicKey and exportOpenSshPrivateKey throw a typed ResultError for not_extractable, unsupported_algorithm, invalid_key or invalid_comment. See handling builder errors for isResultError.

Handle import failures ​

Imports return a result. Branch on result.error.code when accepting uploaded or pasted key material:

CodeMeaning and response
malformedThe file or key encoding is invalid. Ask for the complete, original key file.
unsupportedThe key type, encryption or container is outside the accepted formats, or an RSA key is below your minimum size.
key_mismatchA private file's embedded public and private keys disagree. Reject it.
limit_exceededThe file or key exceeds the size limits below.
invalid_optionsFix the import options supplied by your application.

The complete result and error types are in the SSH API reference.

Formats and import options ​

InputSupported
Public linessh-rsa, ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521
Private fileOPENSSH PRIVATE KEY, openssh-key-v1, one key, cipher and KDF both none

Encrypted OpenSSH files, SSH certificates, authorized_keys options, sk-* keys and DSA are unsupported. This module converts key material. SSH connections and protocol authentication belong to an SSH client.

Import optionDefaultWhen to change it
minRsaModulusBits2048Your application's RSA key-size policy differs.
acceptCompressedEcPointsfalseYou need to read compressed ECDSA points permitted by RFC 5656.
rsa.scheme / rsa.hashPKCS#1 v1.5 / SHA-256The imported RSA CryptoKey will be used with a different signature scheme or hash.

The ssh-rsa label describes a key encoding. It does not select SHA-1 signatures. RSA-SHA2 signature algorithms also use that encoding. The rsa import options select the WebCrypto signature parameters.

Public lines are limited to 16 KiB, private files to 32 KiB, and RSA moduli to 16384 bits. Comments allow at most 8192 UTF-8 bytes and must be well-formed Unicode without CR, LF or NUL.

Private-key consistency and buffer handling

Imports check that the private section describes the file's public key and that a signature from the private key verifies under it. RSA imports also check the relationships between the modulus, prime factors, exponents and CRT coefficient. Ed25519's embedded public-key bytes must agree with the public key.

The decoded private-file buffer is zeroed before import returns. Export zeroes its combined private-field, padded-section and file buffers. Strings and temporary field encodings are managed by JavaScript and cannot all be erased explicitly.

Continue with key conversions, the SSH API, or the SSH error codes.

Released under the MIT License.