Skip to content
/ ssh_key Public

Encodes and decodes keys to/from different text file formats

License

Notifications You must be signed in to change notification settings

hoylen/ssh_key

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ssh_key

Encodes and decodes keys to/from different text file formats.

Introduction

This package allows public keys and private keys to be decoded from different text file formats. And for them to be encoded into those formats.

The primary focus is on the file formats used by implementations of SSH. But some of these formats are also used by other programs; and there is no fundamental reason why other key file formats can't be supported.

SSH implementations use a number of different file formats to represent public-keys. While the SSH wire-protocol is standardized, the storage of keys was an implementation detail that was left undefined. Consequently, different implementations have used different (often proprietary) file formats to store their keys.

Formats

These public key formats are supported:

  • OpenSSH Public Key (single-line format used by older versions of OpenSSH);

  • SSH Public Key (defined by RFC 4716 and used by newer versions of OpenSSH);

  • Textual encoding of PKCS #1 (OpenSSH calls this "PEM", which is an ambiguous term);

  • Textual encoding of subjectPublicKeyInfo from X.509 (OpenSSH incorrectly refers to this as "PKCS #8"; the real PKCS #8 is a format for representing a private key)

Examples of these can be found at the end of this page.

Public-key algorithms

RSA

The RSA public-key algorithm is fully supported.

RSA public keys can be parsed from, and encoded to, any of the above formats.

Generic OpenSSH Public Keys format

Any public keys in the OpenSSH Public Key format (the single-line format) are also supported. But the information in them are treated as binary data. Only RSA public keys are fully parsed into the values represented by the public key.

These public keys can be parsed into a GenericPublicKey object, where the data is only available as a sequence of bytes. But the library is not able to extract the ed25519 parameters from those bytes.

Therefore, the library can be used to store these public key, and to perform some syntax checks on it. But the library cannot be used to perform cryptographic operations with the public key.

Example

This example shows the decoding of a String into either a publc key or a private key, using publicKeyDecode and privateKeyDecode respectively.

It also shows the encoding of a public key and private key into a String, using the encode method on the key.

import 'dart:io';
import 'package:ssh_key/ssh_key.dart' as ssh_key;

void processPublic(String str, bool verbose, ssh_key.PubKeyEncoding? format) {
  // Decode a public key

  final pubKey = ssh_key.publicKeyDecode(str);

  // Output the values in the public key

  if (verbose) {
    if (pubKey is ssh_key.RSAPublicKeyWithInfo) {
      // Use the RSA public key
      //
      // This example just prints out the RSA parameters to stderr (so only
      // encoded output goes to stdout).

      stderr.write('''RSA public key:
  modulus: ${pubKey.n}
  public exponent: ${pubKey.publicExponent}
  fingerprint: ${pubKey.fingerprint()}

''');
    } else {
      stderr.writeln('Error: recognised public key, but not RSA');
      exit(1);
    }
  }

  // Encode the public key

  if (format != null) {
    final destEncoding = pubKey.encode(format);
    stdout.write(destEncoding);
  }
}

void processPrivate(String str, bool verbose, ssh_key.PvtKeyEncoding? format) {
  // Decode a private key

  final privateKey = ssh_key.privateKeyDecode(str);

  // Output the values in the private key

  if (verbose) {
    if (privateKey is ssh_key.RSAPrivateKeyWithInfo) {
      // Use the RSA private key
      //
      // This example just prints out the RSA parameters to stderr (so only
      // encoded output goes to stdout).

      stderr.write('''RSA public key:
  modulus: ${privateKey.modulus}
  public exponent: ${privateKey.publicExponent}
  private exponent: ${privateKey.privateExponent}
  prime1 (p): ${privateKey.p}
  prime2 (q): ${privateKey.q}

''');
    } else {
      stderr.writeln('Error: recognised private key, but not RSA');
      exit(1);
    }
  }

  // Encode the private key

  if (format != null) {
    final destEncoding = privateKey.encode(format);
    stdout.write(destEncoding);
  }
}

/// Command line options

class Options {
  /// Parse the command line arguments.

  Options(List<String> args) {
    bool showHelp = false;
    String? outputFormatArg;
    final filenames = <String>[];

    for (final arg in args) {
      if (arg.startsWith('-')) {
        switch (arg) {
          case '--public':
            isPublic = true;
            break;
          case '--private':
          case '--secret':
          case '-s':
            isPublic = false;
            break;
          case '--verbose':
          case '-v':
            verbose = true;
            break;
          case '--help':
          case '-h':
            showHelp = true;
            break;
          case '--openssh':
          case '--sshpublickey':
          case '--pkcs1':
          case '--x509spki':
          case '--puttyprivatekey':
            outputFormatArg = arg;
            break;
          default:
            stderr.write('Usage error: unknown option: $arg (-h for help)\n');
            exit(2);
        }
      } else {
        filenames.add(arg);
      }
    }

    if (filenames.isEmpty) {
      stderr.write('Usage error: missing filename (-h for help)\n');
      exit(2);
    } else if (filenames.length == 1) {
      filename = filenames.first;
    } else {
      stderr.write('Usage error: too many arguments (-h for help)\n');
      exit(2);
    }

    // Set output format, if requested

    if (isPublic) {
      if (outputFormatArg != null) {
        publicKeyOutFormat = {
          '--openssh': ssh_key.PubKeyEncoding.openSsh,
          '--sshpublickey': ssh_key.PubKeyEncoding.sshPublicKey,
          '--pkcs1': ssh_key.PubKeyEncoding.pkcs1,
          '--x509spki': ssh_key.PubKeyEncoding.x509spki,
        }[outputFormatArg];
        if (publicKeyOutFormat == null) {
          stderr.writeln('Error: $outputFormatArg not for a public key');
          exit(2);
        }
      }
    } else {
      if (outputFormatArg != null) {
        privateKeyOutFormat = {
          '--openssh': ssh_key.PvtKeyEncoding.openSsh,
          '--puttyprivatekey': ssh_key.PvtKeyEncoding.puttyPrivateKey,
          '--pkcs1': ssh_key.PvtKeyEncoding.pkcs1,
        }[outputFormatArg];
        if (privateKeyOutFormat == null) {
          stderr.writeln('Error: $outputFormatArg not for a private key');
          exit(2);
        }
      }
    }

    if (showHelp) {
      stderr.write('''
Usage: example [options] filename
Options:
  --public    file contains a public key (default)
  --private   file contains a private key
  --verbose   show the key parameters

Output format for public keys:  
  --openssh       old OpenSSH public key format (one line)
  --sshpublickey  new OpenSSH public key format (RFC 4716)
  --pkcs1         PKCS#1 public key format
  --x509spki      X.509 SubjectPublicKeyInformation (incorrectly called PKCS#8)

Output format for private keys only:
  --openssh          OpenSSH format (old format)
  --puttyprivatekey  PuTTY Private Key (PPK) format
  --pkcs1            PKCS#1 private key format
''');
      exit(0);
    }
  }

  bool isPublic = true;
  late String filename;

  bool verbose = false;

  ssh_key.PubKeyEncoding? publicKeyOutFormat;
  ssh_key.PvtKeyEncoding? privateKeyOutFormat;
}

void main(List<String> args) {
  // Parse command line arguments

  final options = Options(args);

  // Read the file contents

  final srcEncoding = File(options.filename).readAsStringSync();

  // Parse the contents and output the results

  if (options.isPublic) {
    processPublic(srcEncoding, options.verbose, options.publicKeyOutFormat);
  } else {
    processPrivate(srcEncoding, options.verbose, options.privateKeyOutFormat);
  }
}

Know limitations

  • Only RSA keys are fully supported.

  • Requires Dart 2.7.0 or later, because it uses Dart extension methods to make the encode method available on the Pointy Castle public and private key classes.

  • Private key support is very experimental at this stage. Interoperability is not guaranteed!

    • OpenSSH private key format: medium
    • PuTTY Private Key format: poor
    • PKCS#1 private key format: good
  • Private keys protected by a passphrase are not supported.

This package uses the public-key classes from the Pointy Castle package. Mainly because it has classes to represent RSA keys, and implements symmetric encryption algorithms (which will be needed when support for encrypted private keys is added). In theory, this package could have been independent of any cryptographic package, but it would then have to implement its own classes for representing the keys.

Note: support for other formats is limited by the lack of documentation about the format (since they are often proprietary). Support for other types of keys is limited by the implementation of other cryptographic algorithms in Dart.

Format examples

OpenSSH Public Key

Single-line format used by older versions of OpenSSH.

This format is used in ~/.ssh/known_hosts and ~/.ssh/authorized_keys files.

RSA public key:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYwd6O4Z+STMxTlFCPcN8VAq9ZNKvaQRYbsEDKK0ydvTxdwt72xRo8supYX1vgDgRpYBhgDy8OPEMLDuk61sXRdbTITFW1B98rUsvvLEYHM4wJQnkWZvcyZz79id2H3r75ow+EL6SF4zxrSnJ9Ax09cKN2oM3nQUn0jkaqG4Hb/thbKbF8SzevBrcI0Ld4K64Mduc2XQbW2qMikT4xPBtu7bwPuP1XhipZOBcCBnXdrWCZk6pfYtA/aq5En7a2JAyglIpEsAIbtSVmj62BgstmSOy/4tQjVinh6IG8y8ixq59GbmC8KP9zUQ3hhLfT/nqreXpeh039cotUTWJHyVOB user@example.com'

Ed25519 public key:

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHlm47YRgXlI/6nKEdV6RuXEBLDFdJTfvb/pPEgO6/FE

Elliptic Curve Digital Signature Algorithm:

ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLSZ2G4sjk7qtIVlgx9AZsl3XZtgci0kK54Nef6xxwqas5V3v37DkgIKbER+Yx6GQ7VL3qrfha1pSLmn04qUQg4=

SSH Public Key

Defined by RFC 4716 and used by newer versions of OpenSSH.

---- BEGIN SSH2 PUBLIC KEY ----
Comment: "user@example.com"
AAAAB3NzaC1yc2EAAAADAQABAAABAQDYwd6O4Z+STMxTlFCPcN8VAq9ZNKvaQRYbsEDKK0
ydvTxdwt72xRo8supYX1vgDgRpYBhgDy8OPEMLDuk61sXRdbTITFW1B98rUsvvLEYHM4wJ
QnkWZvcyZz79id2H3r75ow+EL6SF4zxrSnJ9Ax09cKN2oM3nQUn0jkaqG4Hb/thbKbF8Sz
evBrcI0Ld4K64Mduc2XQbW2qMikT4xPBtu7bwPuP1XhipZOBcCBnXdrWCZk6pfYtA/aq5E
n7a2JAyglIpEsAIbtSVmj62BgstmSOy/4tQjVinh6IG8y8ixq59GbmC8KP9zUQ3hhLfT/n
qreXpeh039cotUTWJHyVOB
---- END SSH2 PUBLIC KEY ----

Note: the encapsulation boundary lines have four hyphens and spaces, unlike the textual encoding format which have five hyphens and no spaces.

Textual encoding of PKCS #1

OpenSSH calls this "PEM" (which is an ambiguous term, since PEM is a textual encoding that is used for many types of data: public keys, private keys, certificates, etc.).

-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA2MHejuGfkkzMU5RQj3DfFQKvWTSr2kEWG7BAyitMnb08XcLe9sUa
PLLqWF9b4A4EaWAYYA8vDjxDCw7pOtbF0XW0yExVtQffK1LL7yxGBzOMCUJ5Fmb3
Mmc+/Yndh96++aMPhC+kheM8a0pyfQMdPXCjdqDN50FJ9I5GqhuB2/7YWymxfEs3
rwa3CNC3eCuuDHbnNl0G1tqjIpE+MTwbbu28D7j9V4YqWTgXAgZ13a1gmZOqX2LQ
P2quRJ+2tiQMoJSKRLACG7UlZo+tgYLLZkjsv+LUI1Yp4eiBvMvIsaufRm5gvCj/
c1EN4YS30/56q3l6XodN/XKLVE1iR8lTgQIDAQAB
-----END RSA PUBLIC KEY-----

Textual encoding of subjectPublicKeyInfo from X.509

OpenSSH incorrectly refers to this as "PKCS #8"; the real PKCS #8 is a format for representing a private key.

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2MHejuGfkkzMU5RQj3Df
FQKvWTSr2kEWG7BAyitMnb08XcLe9sUaPLLqWF9b4A4EaWAYYA8vDjxDCw7pOtbF
0XW0yExVtQffK1LL7yxGBzOMCUJ5Fmb3Mmc+/Yndh96++aMPhC+kheM8a0pyfQMd
PXCjdqDN50FJ9I5GqhuB2/7YWymxfEs3rwa3CNC3eCuuDHbnNl0G1tqjIpE+MTwb
bu28D7j9V4YqWTgXAgZ13a1gmZOqX2LQP2quRJ+2tiQMoJSKRLACG7UlZo+tgYLL
Zkjsv+LUI1Yp4eiBvMvIsaufRm5gvCj/c1EN4YS30/56q3l6XodN/XKLVE1iR8lT
gQIDAQAB
-----END PUBLIC KEY-----

Which library to use

Three libraries are provided by this package.

Most programs should only need to use the ssh_key library. The other two libraries are for internal use by the ssh_key library.

import 'package:ssh_key/ssh_key.dart' as ssh_key;

About

Encodes and decodes keys to/from different text file formats

Resources

License

Stars

Watchers

Forks

Packages

No packages published