ExceptionFactory

Producing content that a reasonable developer might want to read

SSHJ Key Authentication Formats

SSH Cryptography Security

2023-04-06 • 9 minute read • David Handermann

Background

The Secure Shell protocol originated in 1995 as an encrypted replacement for services such as Telnet and FTP, enabling confidential access to remote consoles and files. OpenBSD included the first version of OpenSSH in 1999, and the portable version of OpenSSH became the standard for many operating systems based on Linux. RFC 4253 codified SSH 2.0 in 2006, describing common features implemented in OpenSSH and other proprietary software packages. The SSH protocol has grown to encompass multiple specifications and extensions as cryptographic algorithms have evolved. In addition to traditional username and password authentication, SSH supports public key authentication where a client can use an authorized public and private key pair to negotiate access. RFC 4253 Section 6.6 required support for DSA keys and recommended support for RSA keys, but more recent updates such as RFC 5656 and RFC 8709 have introduced alternatives based on elliptic curves. SSH public key authentication involves not only the algorithms themselves but also the formats required to store and read key files.

Introduction

Shikhar Bhushan released SSHJ 0.1.0 in 2010 with support for SSH client operations using either password or public key authentication. Jeroen van Erp has maintained SSHJ since 2015, releasing versions 0.11.0 through 0.35.0 over several years. Although SSH specifications have defined various algorithms for public key authentication, protocol standards do not mandate the storage format for private keys. Popular implementations such as OpenSSH and PuTTY have developed and supported different key formats with various attributes including common headers and optional password-based encryption. Recent versions of SSHJ support a number of file formats, and understanding the internal structure is useful for troubleshooting. Familiarity with these formats can also help evaluate the relative strengths and weakness of various approaches.

Public Key Algorithms

The current set of SSH specifications define the following general algorithms for public key authentication:

The DSA algorithm is disabled by default on modern versions of OpenSSH Server. ECDSA is a newer standard that the National Institute of Standards and Technology approved along with several official elliptic curves. The Edwards-curve Digital Signature Algorithm defines two different elliptic curves, with Ed25519 being the more common option. RSA is the oldest algorithm, but with key sizes of 4096 bits, it still provides strong security and compatibility features.

The default OpenSSH server configuration in recent versions prefers Ed25519 before other public key algorithms, making EdDSA the recommended approach for new deployments. NIST ECDSA algorithms also take precedence over RSA in the default configuration. Servers other than OpenSSH may have different defaults or more limited options, making the choice of algorithms dependent on the server implementation.

Private Key Formats

Private key storage formats are even more varied than SSH public key algorithms. A private key provides access to a server that has authorized the corresponding public key, so protecting private keys is essential to SSH security. For this reason, various software implementations have developed different storage solutions and protection strategies for private key files. OpenSSH and PuTTY are some of the most popular SSH clients, so SSHJ development has focused on private key files compatible with these programs. Although private key files consistently primarily of random characters, all private file formats follow some type of standard encoding.

Privacy-Enhanced Mail Files

Privacy-Enhanced Mail began as a structured encoding format for transmitting keys and certificates over SMTP using ASCII characters. The PEM format consists of simple header and footer lines enclosing content encoded using Base64. RFC 7468 standardized common encoding for private keys and certificates. The Base64-encoded content of PEM files can contain arbitrary binary information, which has made PEM a popular encapsulation format for many data types.

Initial versions of SSHJ supported reading PEM files using the Bouncy Castle library. Bouncy Castle is capable of reading PEM files containing DSA, ECDSA, or RSA keys. Based on generalized PEM processing, Bouncy Castle supports both generic key encoding and structured keys with fields specific to particular algorithms.

Structured Keys for Specific Algorithms

Prior to the introduction of a generalized standard, OpenSSL implemented support for several types of private keys using PEM wrapping. The Bouncy Castle library supports these structured formats with parsing that is specific to each algorithm.

Public Key Cryptography Standard #1 defined in RFC 8017 defines a structure for the elements of an RSA key. RSA private keys formatted according to this structure use the following PEM header:

-----BEGIN RSA PRIVATE KEY-----

RFC 5915 defines a standard structure for elliptic curve private keys, including the following suggested PEM header:

-----BEGIN EC PRIVATE KEY-----

In absence of an official specification, OpenSSL used the following PEM header for DSA private keys, which Bouncy Castle also supports:

-----BEGIN DSA PRIVATE KEY-----

Each of these structures contain raw private key material without additional protection. Supplemental header information can indicate encrypted content with an associated encryption algorithm.

Structured Asymmetric Keys

Public Key Cryptography Standard #8 defined in RFC 5958 outlines generic asymmetric key packaging. The PKCS #8 format supports multiple types of private keys together with optional attributes. PKCS #8 also defines an encrypted wrapping structure for private keys, providing password-based security.

PKCS #8 asymmetric key encoding uses the following PEM header:

-----BEGIN PRIVATE KEY-----

PKCS #8 encrypted asymmetric key encoding, also defined in RFC 5958, uses the following PEM header:

-----BEGIN ENCRYPTED PRIVATE KEY-----

Encrypted private keys include an algorithm field that identifies the password-based encryption strategy required to read the private key information.

SSHJ 0.32.0 introduced support for reading encrypted private key files encoding according to PKCS #8 specifications.

ASN.1 Private Key Information

The encoded content of a PEM private key consists of data structured according to Abstract Syntax Notation One following Distinguished Encoding Rules. ASN.1 with DER uses common tags with length and value attributes to describe data fields. Private key and public certificate standards use these common tags to describe structured objects using ASN.1 syntax.

The encoded private key example from RFC 7468 contains a Private Key Information object with the following ASN.1 module defined in RFC 5208 Section 5:

PrivateKeyInfo ::= SEQUENCE {
  version                 Version,
  privateKeyAlgorithm     AlgorithmIdentifier,
  privateKey              PrivateKey,
  attributes          [0] Attributes OPTIONAL
}

As shown in the module, the PrivateKeyInfo definition includes version and privateKeyAlgorithm fields that describe the key itself. RFC 5208 Section 5 sets the initial version at 0 and imports the AlgorithmIdentifier definition from RFC 8018 Appendix C.

The Algorithm Identifier provides a unique indicator for type of key contained in the privateKey field. The string representation of an Algorithm Identifier consists of a hierarchical set of numbers from general category to specific algorithm. For example, the Algorithm Identifier for RSA encryption is 1.2.840.113549.1.1.1.

The attributes field is optional and can contain additional information about the private key.

RFC 5958 extended the definition of an asymmetric key, renaming PrivateKeyInfo to OneAsymmetricKey and adding an optional publicKey field. RFC 5958 uses a value of 1 to indicate a new version of the module. The addition of the Public Key enables transmission of the complete key pair in single encoded structure as defined in RFC 5958 Section 2:

OneAsymmetricKey ::= SEQUENCE {
  version                  Version,
  privateKeyAlgorithm      AlgorithmIdentifier,
  privateKey               PrivateKey,
  attributes           [0] Attributes OPTIONAL,
  publicKey            [1] PublicKey OPTIONAL
}

RFC 5958 Section 3 defines the structure for an encrypted private key as follows:

EncryptedPrivateKeyInfo ::= SEQUENCE {
  encryptionAlgorithm  EncryptionAlgorithmIdentifier,
  encryptedData        EncryptedData
}

The encryptionAlgorithm field provides a unique reference to the key derivation function and cipher algorithm that protects the private key information.

Using the Bouncy Castle library, SSHJ is capable of reading both standard and encrypted private key information.

OpenSSH Private Key Version 1

OpenSSH 6.5 introduced a new private key encoding structure that departs from other specifications. The OpenSSH PROTOCOL.key document describes the format, which includes a key derivation function based on the bcrypt algorithm for password-based encryption. Private key files formatted using OpenSSH Key Version 1 have the following header:

-----BEGIN OPENSSH PRIVATE KEY-----

Martin Atanasov Nikolov describes the OpenSSH private key binary format in extensive detail, highlighting important processing elements related to specific key algorithms. Similar to PKCS #8, the OpenSSH format is capable of holding private keys for multiple types of algorithms.

The OpenSSH specification includes both public and private keys using a structure with the following fields:

byte[]  AUTH_MAGIC
string  ciphername
string  kdfname
string  kdfoptions
uint32  number of keys
string  publickey
string  list of private keys

The AUTH_MAGIC field consists of the bytes representing openssh-key-v1. The other fields are populated based on the type of key algorithm and whether the private key is encrypted.

Although the OpenSSH format uses ASN.1 with DER for encoding the private key, the format uses length-delimited binary blocks to separate structured fields.

SSHJ 0.19.0 introduced support for OpenSSH Private Key Version 1 and SSHJ 0.27.0 included important updates to support additional algorithms.

The OpenSSH format provides better encrypted protection using bcrypt-based key derivation, at the expense of compatibility with other generalized key storage formats.

PuTTY Private Key Files

PuTTY is a free SSH client that supports both Windows and Unix platforms. The PuTTY Private Key format does not follow Privacy-Enhanced Mail conventions, instead providing a custom structure for storing encrypted and unencrypted private keys. Appendix C of the PuTTY documentation describes the PPK format with details for the current version 3 as well as older versions. Although PPK is less common than other formats, supporting PuTTY private keys enables compatibility without additional format conversion.

PuTTY 0.75.0 introduced PPK version 3, incorporating the modern Argon2 key derivation function for password-based encryption. PPK version 2 employed a custom key derivation strategy, and PPK version 1 was limited to initial development versions of PuTTY.

SSHJ 0.11.0 introduced support for PPK versions 1 and 2. SSHJ 0.32.0 added support for PPK version 3.

The PPK format uses multiple header lines to describe the contents, and encodes both public and private keys using Base64. The first line of a PPK file indicates both the version and the SSH key algorithm as follows:

PuTTY-User-Key-File-3: ssh-ed25519

Subsequent lines indicate the encryption algorithm, which can be none or aes256-cbc as of PuTTY 0.78.0.

The PPK format includes a Message Authentication Code derived from the private key in versions 1 and 2, and derived from the entire file content in version 3. PPK version 3 also replaced HMAC-SHA-1 with HMAC-SHA-256 as the digest algorithm.

Loading Private Keys

Although the combination of formats, encodings, and algorithms introduces a great deal of complexity, SSHJ provides straightforward methods for loading private keys.

The central SSHClient class includes a number of methods for loading private keys. The following method supports loading an unencrypted private key from a file location with automatic detection of supported formats:

public KeyProvider loadKeys(String location)

The loadKeys method also accepts a passphrase argument to support reading an encrypted private key:

public KeyProvider loadKeys(String location, char[] passphrase)

These methods make use of the KeyProviderUtil class for file format detection.

Instances of KeyProvider can be supplied to methods on SSHClient to complete the authentication process.

Conclusion

SSH continues to serve as a common protocol for secure remote access and data transfer. With ongoing maintenance and community contributions, SSHJ has grown to support an array of public key authentication algorithms and private key storage formats. Knowledge of the formats behind different file headers enables troubleshooting and supports format conversion when necessary. Although some algorithms and formats no longer provide optimal security guarantees, SSHJ is capable of supporting both legacy options and modern solutions.