将pem密钥转换为ssh-rsa格式

201

我有一个以der格式的证书,我可以使用以下命令生成公钥:

openssl x509 -inform der -in ejbcacert.cer -noout -pubkey > pub1key.pub

这将导致以下结果:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7vbqajDw4o6gJy8UtmIbkcpnk
O3Kwc4qsEnSZp/TR+fQi62F79RHWmwKOtFmwteURgLbj7D/WGuNLGOfa/2vse3G2
eHnHl5CB8ruRX9fBl/KgwCVr2JaEuUm66bBQeP5XeBotdR4cvX38uPYivCDdPjJ1
QWPdspTBKcxeFbccDwIDAQAB
-----END PUBLIC KEY-----

我该如何获取像这样的公钥?可以通过证书或从此公钥获得吗?

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC7vbqajDw4o6gJy8UtmIbkcpnkO3Kwc4qsEnSZp/TR+fQi62F79RHWmwKOtFmwteURgLbj7D/WGuNLGOfa/2vse3G2eHnHl5CB8ruRX9fBl/KgwCVr2JaEuUm66bBQeP5XeBotdR4cvX38uPYivCDdPjJ1QWPdspTBKcxeFbccDw==

这是通过以下命令获取的:

ssh-keygen -y -f private_key1.pem > public_key1.pub

17
你在“这是通过这个命令获得的”这篇帖子中发布的方法对我来说比下面任何一个答案都更有效。 - Yoav Shapira
11
是的,但整个问题在于他想要仅使用公钥进行转换。也许他没有私钥,只有公钥,并且希望将格式从PEM转换为ssh-rsa。 - deltamind106
13
从AWS获取一个.pem文件后,您上面给出的命令 ssh-keygen -y -f private_key1.pem > public_key1.pub 对我非常有效。 - Kzqai
4
所有错误答案。这个才是正确的:ssh-keygen -i -m PKCS8 -f public-key.pem - Boeboe
4
「爱美在心,不在颜」。我们需要注意的是,一个 pem 密钥可以包含公钥、私钥或两者兼有;加密或未加密;同时还有多种格式。此外,选项“-m”的含义对于“-i”/“-e”是不同的。所以,朋友们,请确保您知道自己想要什么,拥有什么。 :-) - ryenus
显示剩余4条评论
12个回答

180
不需要编译任何东西。您可以使用 ssh-keygen 来完成相同的操作:
ssh-keygen -f pub1key.pub -i

将从pub1key.pub中以openssl格式读取公钥,并以OpenSSH格式输出。

注意:在某些情况下,您需要指定输入格式:

ssh-keygen -f pub1key.pub -i -m PKCS8

来自ssh-keygen文档(来自man ssh-keygen):

-m key_format 指定-i(导入)或-e(导出)转换选项的密钥格式。支持的密钥格式有: “RFC4716”(RFC 4716/SSH2公钥或私钥),“PKCS8”(PEM PKCS8公钥)或“PEM”(PEM公钥)。默认转换格式为“RFC4716”。


4
ssh-keygen:非法选项--m - mbonnin
2
问题的方向相反。 - 131
24
在我的情况下,-m PKCS8 是必要的。 - Ian Hunter
1
$ ssh-keygen -f mykey.pub -i key_from_blob: invalid format decode blob failed. - Bastian Voigt
1
我重新添加了更具体、更长的格式到答案中,因为这是我和一些其他人所要求的。似乎哪种方法适用取决于您使用的ssh-keygen版本和/或输入格式。 - Ogre Codes
输出在哪里?命令执行时没有输出。 - Andrew Rebane

73
不需要脚本或其他“技巧”:只需使用opensslssh-keygen即可。我假设密钥没有密码(这是不好的)。

生成RSA密钥对

以下所有方法都以相同格式提供RSA密钥对。

  1. With openssl (man genrsa)

    openssl genrsa -out dummy-genrsa.pem 2048
    

    In OpenSSL v1.0.1 genrsa is superseded by genpkey so this is the new way to do it (man genpkey):

    openssl genpkey -algorithm RSA -out dummy-genpkey.pem -pkeyopt rsa_keygen_bits:2048
    
  2. With ssh-keygen

    ssh-keygen -t rsa -b 2048 -f dummy-ssh-keygen.pem -N '' -C "Test Key"
    

将DER转换为PEM

如果您拥有DER格式的RSA密钥对,您可能想将其转换为PEM格式,以便进行下面的格式转换:

生成:

openssl genpkey -algorithm RSA -out genpkey-dummy.cer -outform DER -pkeyopt rsa_keygen_bits:2048

转换:

openssl rsa -inform DER -outform PEM -in genpkey-dummy.cer -out dummy-der2pem.pem

从PEM格式的RSA对中提取公钥
  1. in PEM format:

    openssl rsa -in dummy-xxx.pem -pubout
    
  2. in OpenSSH v2 format see:

    ssh-keygen -y -f dummy-xxx.pem
    

注意事项

操作系统和软件版本:

[user@test1 ~]# cat /etc/redhat-release ; uname -a ; openssl version
CentOS release 6.5 (Final)
Linux test1.example.local 2.6.32-431.el6.x86_64 #1 SMP Fri Nov 22 03:15:09 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
OpenSSL 1.0.1e-fips 11 Feb 2013

参考资料:


// 这是否实际生成 ssh-rsa 格式的密钥?顺便说一句,参考资料不错。 - Nathan Basanese
@NathanBasanese,是的(请参见“从PEM格式的RSA对中提取公钥”,第2点):一旦有了pem格式的证书:ssh-keygen -y -f dummy-xxx.pem会生成一个适用于ssh的authorized_keys文件的ssh-rsa AAAA[...]== - Thomas B in BDX
好的信息性文章...但我认为它并没有像上面那篇更短的文章那样很好地回答问题。 - Ogre Codes
通过转换,显示:无法加载私钥 10828 错误:0D0680A8:asn1 编码例程:ASN1_CHECK_TLEN:错误的标记:.\crypto\a sn1\tasn_dec.c:1200: - Max Kleiner
写出:OpenSSL> rsa -in genpkeySSH.pem -pubout -out genpubkeySSH.pem - Max Kleiner

30
回答自己的问题,发了邮件到openssl邮件列表后收到了这个回复:
这是一个将OpenSSL公钥转换为OpenSSH公钥的C代码。你可以从此链接获取代码并编译它:
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;
   // correct depending on the 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;
}

5
如果有人想知道如何编译这个程序(我曾经有过这样的想法),以下是编译器的调用方式:gcc -o pubkey2ssh pubkey2ssh.c -lcrypto。 - Andreas Gohr
如何获取argv [2](ssh_key_description)?我只有一个-----BEGIN RSA PUBLIC KEY-----MIGJAoGBAMC62xWiOZYlhUhmk+JESy5eZunwGoG9kSHUMn67iBNZLEsR2qN44J1BTOtZRuEsSAKxu7alFlJVu5aSGbUvin3DusYAsl5sZjTf9VZgJHsVycOrtChC1tUiWMAWfv2BLTmK4zBEC33riEBLeX8Trphp3YbIMtzqV81ZrzHZbSnrAgMBAAE=-----END RSA PUBLIC KEY-----它没有描述。 - braden
@braden。通常情况下,它只是密钥所有者的电子邮件地址。但您可以在描述中放置任何内容。 - deltamind106
一个 PHP 实现的 OpenSSLToPEM 可以在这里找到 https://github.com/131/yks/blob/master/class/stds/crypt.php#L346 - 131
@mkalkov的答案使用Linux命令行工具进行转换。它只需要公钥PEM文件,去掉头部并合并行作为输入。 - alexandroid

22
ssh-keygen -i -m PKCS8 -f public-key.pem

5
对我来说没有用:“do_convert_from_pkcs8:key.pem不是公钥格式”。可行的方法是“ssh-keygen -y -f key.pem”,它会打印出所需的ssh-rsa文本以用于authorized_keys。 - Curt
1
这不起作用:do_convert_from_pkcs8: TEST.pem 不是已知的公钥格式 - Jinna Balu
在执行了 openssl genrsa -out newkey.pem 2048openssl rsa -in newkey.pem -outform PEM -pubout -out newkeypublic.pem 后,对我很有效。 - xirix

17
ssh-keygen -f private.pem -y > public.pub

8
以下脚本将以base64编码的DER格式获取ci.jenkins-ci.org公钥证书,并将其转换为OpenSSH公钥文件。该代码假定使用2048位RSA密钥,并且借鉴了Ian Boyd的答案。我在Jenkins维基的这篇文章的评论中更详细地解释了它的工作原理。
echo -n "ssh-rsa " > jenkins.pub
curl -sfI https://ci.jenkins-ci.org/ | grep -i X-Instance-Identity | tr -d \\r | cut -d\  -f2 | base64 -d | dd bs=1 skip=32 count=257 status=none | xxd -p -c257 | sed s/^/00000007\ 7373682d727361\ 00000003\ 010001\ 00000101\ / | xxd -p -r | base64 -w0 >> jenkins.pub
echo >> jenkins.pub

1
哇塞,这是最好的答案!而且它还有效!(我只需要将status=none替换为status=noxfer)。只需使用以“base64”开头的第二个命令,并在输入中使用一个已剥离标题并将所有行连接成一个的PEM文件。谢谢@mkalkov! - alexandroid
请注意,上面的命令假定使用2048位密钥,如果给出不同大小的密钥,则命令将无法正常工作。 - alexandroid

7

我用以下命令进行操作:

ssh-keygen -i -f $sshkeysfile >> authorized_keys

参考链接


1
你为什么没有给Victor注明来源呢?他在将近8个月之前就给了你同样的指令。 - jww
1
@jww 从 Victor 回复的编辑日志中可以看到,最初的答案有些不同,我认为这就是原因。 - periklis

4

这个BASH脚本可以将PEM或DER格式的X.509证书或OpenSSL公钥文件(也是PEM格式)作为第一个参数,并输出OpenSSH RSA公钥。这扩展了@mkalkov上面的答案。所需工具包括catgreptrddxxdsedxargsfileuuidgenbase64openssl(1.0+),当然还需要bash。除了openssl(包含base64)以外,几乎保证所有现代Linux系统的基本安装中都包含这些工具,除了可能是xxd(Fedora显示在vim-common包中)。如果有人想要整理并使其更好,需要注意。

#!/bin/bash
#
# Extract a valid SSH format public key from an X509 public certificate.
#

# Variables:
pubFile=$1
fileType="no"
pkEightTypeFile="$pubFile"
tmpFile="/tmp/`uuidgen`-pkEightTypeFile.pk8"

# See if a file was passed:
[ ! -f "$pubFile" ] && echo "Error, bad or no input file $pubFile." && exit 1

# If it is a PEM format X.509 public cert, set $fileType appropriately:
pemCertType="X$(file $pubFile | grep 'PEM certificate')"
[ "$pemCertType" != "X" ] && fileType="PEM"

# If it is an OpenSSL PEM-format PKCS#8-style public key, set $fileType appropriately:
pkEightType="X$(grep -e '-BEGIN PUBLIC KEY-' $pubFile)"
[ "$pkEightType" != "X" ] && fileType="PKCS"

# If this is a file we can't recognise, try to decode a (binary) DER-format X.509 cert:
if [ "$fileType" = "no" ]; then
        openssl x509 -in $pubFile -inform DER -noout
        derResult=$(echo $?)
        [ "$derResult" = "0" ] && fileType="DER"
fi

# Exit if not detected as a file we can use:
[ "$fileType" = "no" ] && echo "Error, input file not of type X.509 public certificate or OpenSSL PKCS#8-style public key (not encrypted)." && exit 1

# Convert the X.509 public cert to an OpenSSL PEM-format PKCS#8-style public key:
if [ "$fileType" = "PEM" -o "$fileType" = "DER" ]; then
        openssl x509 -in $pubFile -inform $fileType -noout -pubkey > $tmpFile
        pkEightTypeFile="$tmpFile"
fi

# Build the string:
# Front matter:
frontString="$(echo -en 'ssh-rsa ')"

# Encoded modulus and exponent, with appropriate pointers:
encodedModulus="$(cat $pkEightTypeFile | grep -v -e "----" | tr -d '\n' | base64 -d | dd bs=1 skip=32 count=257 status=none | xxd -p -c257 | sed s/^/00000007\ 7373682d727361\ 00000003\ 010001\ 00000101\ / | xxd -p -r | base64 -w0 )"

# Add a comment string based on the filename, just to be nice:
commentString=" $(echo $pubFile | xargs basename | sed -e 's/\.crt\|\.cer\|\.pem\|\.pk8\|\.der//')"

# Give the user a string:
echo $frontString $encodedModulus $commentString

# cleanup:
rm -f $tmpFile

3

以下是适合我的方法,因为我只有公钥访问权限:

  1. 将PEM公钥转换为PKCS8兼容的公钥
    openssl x509 -pubkey -noout -in pubcertkey.pem > pubcertkey.pub

将PKCS8公钥转换为ssh-rsa密钥
    ssh-keygen -i -mPKCS8 -f pubcertkey.pub > pubcertkey-ssh-rsa.pub

0

请注意,即使是当前的Win32-OpenSSH版本似乎也存在一个错误,阻止了此转换的发生,如这个专用的GitHub问题页面所述。

可以通过以下行为确认问题:

  • 命令的输出为空(但没有打印错误): Blank output
  • 在“Windows日志/应用程序”日志中可以找到错误:Error in "Applications" journal

唯一的替代方案似乎是不使用Win32-OpenSSH来完成此特定任务。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接