Keywords: PEM Public Key | SSH-RSA Format | OpenSSL Conversion | Key Format | Encrypted Communication
Abstract: This paper provides an in-depth exploration of converting OpenSSL-generated PEM format public keys to OpenSSH-compatible SSH-RSA format. By analyzing core conversion principles, it details the simplified approach using ssh-keygen tools and presents complete C language implementation code demonstrating the underlying data structure processing of RSA keys. The article also discusses differences between various key formats and practical application scenarios, offering comprehensive technical reference for system administrators and developers.
Technical Background of Key Format Conversion
In modern encrypted communication systems, Public Key Infrastructure (PKI) utilizes various different key formats. The PEM (Privacy-Enhanced Mail) format public keys generated by OpenSSL tools differ significantly in structure from the SSH-RSA format used by OpenSSH. PEM format employs Base64-encoded ASN.1 data structures, while SSH-RSA format uses a specific binary encoding scheme.
Core Conversion Principles
The SSH-RSA format public key essentially contains three key components: key type identifier, RSA public exponent (e), and RSA modulus (n). The conversion process requires extracting these parameters from the PEM format and re-encoding them according to the SSH protocol specifications.
Specifically, the SSH-RSA format structure is: ssh-rsa <base64_encoded_data> <comment>, where base64_encoded_data contains the concatenation of:
Length of string "ssh-rsa" (4 bytes)
String "ssh-rsa" (7 bytes)
Length of public exponent e (4 bytes)
Public exponent e (variable length)
Length of modulus n (4 bytes)
Modulus n (variable length)
Simplified Method Using ssh-keygen Tool
For most practical application scenarios, using the ssh-keygen tool bundled with OpenSSH provides the simplest and most direct conversion method. This tool can automatically handle PEM to SSH-RSA format conversion:
ssh-keygen -f pub1key.pub -i
In some cases, it may be necessary to specify the input format to ensure proper parsing:
ssh-keygen -f pub1key.pub -i -m PKCS8
This approach works with standard PEM public key files and can quickly generate OpenSSH-compatible key formats.
C Language Implementation Code Analysis
To deeply understand the conversion process, we can analyze the complete C language implementation. The following code demonstrates the manual conversion process from PEM public key to SSH-RSA format:
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static unsigned char pSshHeader[11] = { 0x00, 0x00, 0x00, 0x07, 0x73, 0x73, 0x68, 0x2D, 0x72, 0x73, 0x61};
static int SshEncodeBuffer(unsigned char *pEncoding, int bufferLen, unsigned char* pBuffer)
{
int adjustedLen = bufferLen, index;
if (*pBuffer & 0x80)
{
adjustedLen++;
pEncoding[4] = 0;
index = 5;
}
else
{
index = 4;
}
pEncoding[0] = (unsigned char) (adjustedLen >> 24);
pEncoding[1] = (unsigned char) (adjustedLen >> 16);
pEncoding[2] = (unsigned char) (adjustedLen >> 8);
pEncoding[3] = (unsigned char) (adjustedLen );
memcpy(&pEncoding[index], pBuffer, bufferLen);
return index + bufferLen;
}
int main(int argc, char** argv)
{
int iRet = 0;
int nLen = 0, eLen = 0;
int encodingLength = 0;
int index = 0;
unsigned char *nBytes = NULL, *eBytes = NULL;
unsigned char* pEncoding = NULL;
FILE* pFile = NULL;
EVP_PKEY *pPubKey = NULL;
RSA* pRsa = NULL;
BIO *bio, *b64;
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
if (argc != 3)
{
printf("usage: %s public_key_file_name ssh_key_description\n", argv[0]);
iRet = 1;
goto error;
}
pFile = fopen(argv[1], "rt");
if (!pFile)
{
printf("Failed to open the given file\n");
iRet = 2;
goto error;
}
pPubKey = PEM_read_PUBKEY(pFile, NULL, NULL, NULL);
if (!pPubKey)
{
printf("Unable to decode public key from the given file: %s\n", ERR_error_string(ERR_get_error(), NULL));
iRet = 3;
goto error;
}
if (EVP_PKEY_type(pPubKey->type) != EVP_PKEY_RSA)
{
printf("Only RSA public keys are currently supported\n");
iRet = 4;
goto error;
}
pRsa = EVP_PKEY_get1_RSA(pPubKey);
if (!pRsa)
{
printf("Failed to get RSA public key : %s\n", ERR_error_string(ERR_get_error(), NULL));
iRet = 5;
goto error;
}
// Reading the modulus
nLen = BN_num_bytes(pRsa->n);
nBytes = (unsigned char*) malloc(nLen);
BN_bn2bin(pRsa->n, nBytes);
// Reading the public exponent
eLen = BN_num_bytes(pRsa->e);
eBytes = (unsigned char*) malloc(eLen);
BN_bn2bin(pRsa->e, eBytes);
encodingLength = 11 + 4 + eLen + 4 + nLen;
// Adjust based on MSB of e and N
if (eBytes[0] & 0x80)
encodingLength++;
if (nBytes[0] & 0x80)
encodingLength++;
pEncoding = (unsigned char*) malloc(encodingLength);
memcpy(pEncoding, pSshHeader, 11);
index = SshEncodeBuffer(&pEncoding[11], eLen, eBytes);
index = SshEncodeBuffer(&pEncoding[11 + index], nLen, nBytes);
b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bio = BIO_new_fp(stdout, BIO_NOCLOSE);
BIO_printf(bio, "ssh-rsa ");
bio = BIO_push(b64, bio);
BIO_write(bio, pEncoding, encodingLength);
BIO_flush(bio);
bio = BIO_pop(b64);
BIO_printf(bio, " %s\n", argv[2]);
BIO_flush(bio);
BIO_free_all(bio);
BIO_free(b64);
error:
if (pFile)
fclose(pFile);
if (pRsa)
RSA_free(pRsa);
if (pPubKey)
EVP_PKEY_free(pPubKey);
if (nBytes)
free(nBytes);
if (eBytes)
free(eBytes);
if (pEncoding)
free(pEncoding);
EVP_cleanup();
ERR_free_strings();
return iRet;
}
Key Technical Points in Code Implementation
The C language code above demonstrates several important technical details:
1. SSH Header Processing: The code uses a predefined 11-byte header pSshHeader to represent the ssh-rsa string and its length information.
2. Big Integer Encoding: The SshEncodeBuffer function is responsible for converting big integers (modulus n and exponent e) to the format required by the SSH protocol, paying special attention to cases where the most significant bit (MSB) is 1.
3. OpenSSL API Usage: The code fully utilizes OpenSSL library APIs to read and parse PEM format public key files, including functions like PEM_read_PUBKEY and EVP_PKEY_get1_RSA.
4. Base64 Encoding: Uses OpenSSL's BIO interface for Base64 encoding, ensuring the output format complies with SSH-RSA standards.
Practical Application Scenarios and Considerations
In cloud computing environments like AWS EC2 instances, correct key format conversion is crucial. Reference articles mention that EC2 instances use specific .pem files for authentication, and if the original file is lost, instance access cannot be recovered.
During the conversion process, attention should be paid to:
• Ensuring the input public key file format is correct
• Verifying that the generated SSH-RSA key is compatible with the target system
• Considering using tool methods rather than manual encoding in production environments
By understanding these conversion principles and technical implementations, developers can better handle key compatibility issues between different systems, ensuring secure communication proceeds smoothly.