使用 Bouncy Castle 将 RSA 公钥转换为 RFC 4716 公钥

Posted

技术标签:

【中文标题】使用 Bouncy Castle 将 RSA 公钥转换为 RFC 4716 公钥【英文标题】:Converting an RSA Public Key into a RFC 4716 Public Key with Bouncy Castle 【发布时间】:2019-03-16 14:49:58 【问题描述】:

我希望将 RSA 公钥转换为可以用作 SSH 公钥的东西。

目前我有 Bouncy Castle 为我生成一个如下所示的公钥:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq1Y5300i8bN+cI2U3wJE
Kh3xG/.........jbuz+WB0vvG
P25UwCle2k5siVMwbImEYsr+Xt0dsMmGVB3/6MHAqrM3QQdQ8p2E5TyzL+JYa1FT
gwIDAQAB
-----END PUBLIC KEY-----

我希望它具有类似于此的 RFC 4716 格式:

ssh-rsa AAAAB3NzaC1yc2.......G1p2Ag3mZLFsks7RNHVLgMsGIAikQ==

到目前为止,我使用 Bouncy Castle 的代码如下所示:

var r = new Org.BouncyCastle.Crypto.Generators.RsaKeyPairGenerator();
r.Init(new KeyGenerationParameters(new SecureRandom(), 2048));
var keys = r.GenerateKeyPair();

var stringWriter = new StringWriter();

var pemWriter = new PemWriter(stringWriter);
pemWriter.WriteObject(keys.Private);
pemWriter.Writer.Flush();
stringWriter.Close();
PrivateKey = stringWriter.ToString();

stringWriter = new StringWriter();
pemWriter = new PemWriter(stringWriter);
pemWriter.WriteObject(keys.Public);
pemWriter.Writer.Flush();
stringWriter.Close();
PublicKey = stringWriter.ToString();

如何重新格式化和编码密钥以使其看起来像这样?

有没有人使用 Bouncy Castle 或类似工具创建 SSH 公钥?

【问题讨论】:

【参考方案1】:

我在 BouncyCastle 中发现没有现成的功能。因此,解决方法是使用PemReader然后格式化结果。结果将作为PublicSSH 属性提供:

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System;
using System.IO;
using System.Text;

namespace Deploy4Me.Common.Utils

    public class RSAKey
    
        public string PublicPEM  get; set; 
        public string PrivatePEM  get; set; 
        public string PublicSSH  get; set; 
    

    public static class RSA
    
        public static RSAKey Generate()
        
            try
            
                RSAKey result = new RSAKey();
                IAsymmetricCipherKeyPairGenerator gen;
                KeyGenerationParameters param;
                gen = new RsaKeyPairGenerator();
                param = new RsaKeyGenerationParameters(
                    BigInteger.ValueOf(3L),
                    new SecureRandom(),
                    2048,
                    80
                );
                gen.Init(param);
                AsymmetricCipherKeyPair pair = gen.GenerateKeyPair(); 
                using(TextWriter textWriter = new StringWriter())
                
                    PemWriter wr = new PemWriter(textWriter);
                    wr.WriteObject(pair.Private);
                    wr.Writer.Flush();

                    result.PrivatePEM = textWriter.ToString();
                

                using (TextWriter textWriter = new StringWriter())
                
                    PemWriter wr = new PemWriter(textWriter);
                    wr.WriteObject(pair.Public);
                    wr.Writer.Flush();

                    result.PublicPEM = textWriter.ToString();
                

                using (StringReader sr = new StringReader(result.PublicPEM))
                
                    PemReader reader = new PemReader(sr);
                    RsaKeyParameters r = (RsaKeyParameters)reader.ReadObject();
                    byte[] sshrsa_bytes = Encoding.Default.GetBytes("ssh-rsa");
                    byte[] n = r.Modulus.ToByteArray();
                    byte[] e = r.Exponent.ToByteArray();

                    string buffer64;
                    using(MemoryStream ms = new MemoryStream())
                        ms.Write(ToBytes(sshrsa_bytes.Length), 0, 4);
                        ms.Write(sshrsa_bytes, 0, sshrsa_bytes.Length);
                        ms.Write(ToBytes(e.Length), 0, 4);
                        ms.Write(e, 0, e.Length);
                        ms.Write(ToBytes(n.Length), 0, 4);
                        ms.Write(n, 0, n.Length);
                        ms.Flush();
                        buffer64 = Convert.ToBase64String(ms.ToArray());
                    

                    result.PublicSSH = string.Format("ssh-rsa 0 generated-key", buffer64);
                

                return result;
            
            catch (Org.BouncyCastle.Crypto.CryptoException ex)
            
                throw ex;
            
        

        private static byte[] ToBytes(int i)
        
            byte[] bts = BitConverter.GetBytes(i);
            if (BitConverter.IsLittleEndian)
            
                Array.Reverse(bts);
            
            return bts;
        
    

【讨论】:

同意 Doug - 非常好,并且允许我生成用于 SFTP 的密钥。【参考方案2】:

注意:我在微软工作,但这不是微软的回答,只是个人的。

添加到 Pavels 答案,

我发现由于某种原因在生成 3072 位 RSA 密钥时,PuttyGen 会生成与我的不同的 RSA 公钥。

经过研究,我发现在Putty Gen Program的源代码中似乎会对Byte数组做.Length + 1,添加一个前导0。

对于 BouncyCastle,您可以更改此行。

                    ms.Write(ToBytes(n.Length), 0, 4);
                    ms.Write(n, 0, n.Length);

                    ms.Write(ToBytes(n.Length+1), 0, 4);//Add +1 to Emulate PuttyGen
                    ms.Write(new byte[]  0 , 0, 1); //Add a 0 to Emulate PuttyGen
                    ms.Write(n, 0, n.Length);

对于 Microsoft .net RSACryptoServiceProvider,它看起来像这样

            RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(3072);
            byte[] sshrsa_bytes = Encoding.Default.GetBytes("ssh-rsa");
            byte[] n = RSA.ExportParameters(false).Modulus;
            byte[] e = RSA.ExportParameters(false).Exponent;
            string buffer64;
            using (MemoryStream ms = new MemoryStream())
            
                ms.Write(ToBytes(sshrsa_bytes.Length), 0, 4);
                ms.Write(sshrsa_bytes, 0, sshrsa_bytes.Length);
                ms.Write(ToBytes(e.Length), 0, 4);
                ms.Write(e, 0, e.Length);
                ms.Write(ToBytes(n.Length+1), 0, 4); //Remove the +1 if not Emulating Putty Gen
                ms.Write(new byte[]  0 , 0, 1); //Add a 0 to Emulate PuttyGen
                ms.Write(n, 0, n.Length);
                ms.Flush();
                buffer64 = Convert.ToBase64String(ms.ToArray());
            

            string pubssh = string.Format("ssh-rsa 0 generated-key", buffer64);

你可以看到我用于测试的私钥和putty gen源代码链接https://www.cameronmoten.com/2017/12/21/rsacryptoserviceprovider-create-a-ssh-rsa-public-key/

【讨论】:

【参考方案3】:

意识到您的帖子已经发布了几个月,但如果您仍在寻找,请尝试下面的代码 sn-p,其灵感来自 Using public key from authorized_keys with Java security 上的 gotoalberto ...

public static String getPublicOpenSSHKey(String pem, String userComment)
   throws IOException, EWAException

    // Read the PEM supplied using Bouncy Castle's PEMReader ...
    PEMReader r = new PEMReader(new StringReader(pem));
    try  keyPair = (KeyPair) r.readObject(); 
    catch (IOException ioe)  ioe.printStackTrace(); 
    finally  try  r.close();  catch (Throwable ignore)   

    PublicKey publicKey = keyPair.getPublic();

    if (publicKey.getAlgorithm().equals("RSA"))
    
        RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
        ByteArrayOutputStream byteOs = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(byteOs);
        dos.writeInt("ssh-rsa".getBytes().length);
        dos.write("ssh-rsa".getBytes());
        dos.writeInt(rsaPublicKey.getPublicExponent().toByteArray().length);
        dos.write(rsaPublicKey.getPublicExponent().toByteArray());
        dos.writeInt(rsaPublicKey.getModulus().toByteArray().length);
        dos.write(rsaPublicKey.getModulus().toByteArray());
        String enc = Base64.encode(byteOs.toByteArray());
        return("ssh-rsa " + enc + " " + userComment);
     
     else
        throw new IllegalArgumentException("Unknown public key encoding: " + publicKey.getAlgorithm());

【讨论】:

这在 .net 中根本不起作用,并且似乎只是使用完全不同的对象名称。 它可能在 .net 中不起作用,因为它是 Java。

以上是关于使用 Bouncy Castle 将 RSA 公钥转换为 RFC 4716 公钥的主要内容,如果未能解决你的问题,请参考以下文章

仅使用 Bouncy Castle 读取 PEM RSA 公钥

无法将 PBEWithHmacSHA1AndDESede 与 Bouncy Castle fips jar 一起使用,可与 RSA cryptoJ 一起使用

使用 RSA Bouncy Castle 加密/解密无法正常工作

Bouncy Castle 从公钥加密会话数据包中提取 PGP 会话密钥

在没有 Bouncy Castle 的情况下,如何在 java 中从私有(ecdsa)生成公钥?

Bouncy Castle PGP 解密问题