C# RSA公钥输出不正确

26

我目前正在尝试使用C#生成并发送一个公共的RSA密钥。它应该是2048位长的PEM格式密钥。我已经成功地使用OpenSSL命令执行了此操作,命令如下(有些输出被缩短):

$ openssl genrsa 2048 
Generating RSA private key, 2048 bit long modulus
............................................................+++
............................................................+++
e is 65537 (0x10001)
$ openssl rsa -pubout
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAy1MoBtENHBhYLgwP5Hw/xRGaBPHonApChBPBYD6fiq/QoLXA
RmyMoOjXHsKrrwysYIujXADM2LZ0MlFvPbBulvciWnZwp9CUQPwsZ8xnmBWlHyru
xTxNSvV+E/6+2gMOn3I4bmOSIaLx2Y7nCuaenREvD7Mn0vgFnP7yaN8/9va4q8Lo
...
...
y5jiKQKBgGAe9DlkYvR6Edr/gzd6HaF4btQZf6idGdmsYRYc2EMHdRM2NVqlvyLc
MR6rYEuViqLN5XWK6ITOlTPrgAuU6Rl4ZpRlS1ZrfjiUS6dzD/jtJJvsYByC7ZoU
NxIzB0r1hj0TIoedu6NqfRyJ6Fx09U5W81xx77T1EBSg4OCH7eyl
-----END RSA PRIVATE KEY-----
writing RSA key
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy1MoBtENHBhYLgwP5Hw/
xRGaBPHonApChBPBYD6fiq/QoLXARmyMoOjXHsKrrwysYIujXADM2LZ0MlFvPbBu
lvciWnZwp9CUQPwsZ8xnmBWlHyruxTxNSvV+E/6+2gMOn3I4bmOSIaLx2Y7nCuae
nREvD7Mn0vgFnP7yaN8/9va4q8LoMKlceE5fSYl2QIfC5ZxUtkblbycEWZHLVOkv
+4Iz0ibD8KGo0PaiZl0jmn9yYXFy747xmwVun+Z4czO8Nu+OOVxsQF4hu1pKvTUx
9yHH/vk5Wr0I09VFyt3BT/RkecJbAAWB9/e572T+hhmmJ08wCs29oFa2Cdik9yyE
2QIDAQAB
-----END PUBLIC KEY-----
以下的代码是我使用C#生成公钥的方法:
// Variables
CspParameters cspParams = null;
RSACryptoServiceProvider rsaProvider = null;
StreamWriter publicKeyFile = null;
string publicKey = "";

try
{
   // Create a new key pair on target CSP
   cspParams = new CspParameters();
   cspParams.ProviderType = 1; // PROV_RSA_FULL
   cspParams.Flags = CspProviderFlags.CreateEphemeralKey;
   rsaProvider = new RSACryptoServiceProvider(2048, cspParams);

   // Export public key
   result = ExportPublicKeyToPEMFormat(rsaProvider);
}
catch (Exception ex)
{
}

ExportPublicKeyToPEMFormat可以在这个帖子中找到: https://stackoverflow.com/a/25591659/2383179

我的C#输出如下:

-----BEGIN PUBLIC KEY-----
MIIBKwIBAAKCAQEAzMoaInPQ7nAXGWUY2EEtBcPY/Zvfcqf3Uxr7mFrQaxMjdXYi
DVSPh9XBWJlEhQ9ZGyBMpkWwtkrlDw11g/7pj+u7KTa5nH1ZB8vCrY3TC+YnFXPQ
Nv5dCzW0Lz+HD04rir2+K++XQCroy7G68uE9dtkbqa1U7IEWOvejbX+sgzo5ISHA
vCz2DFBInqYNJWfkM8OvLnRYYQ4f8MbmvDEMyaEYPGfQybXAs5eFksqm9pwR0xh4
Oxg/DkDas93lNIf+g00IesHvHuavRm2GX8jAXhrAoZY7nWQZpqS5kwx1kjSwtYEg
Vq4mHcaKIalMAoILSV9ttgqiJ5KVuKIvQJ7wRwIDAQABAgMBAAECAwEAAQIDAQAB
AgMBAAECAwEAAQIDAQAB
-----END PUBLIC KEY-----

使用OpenSSL的正确输出如下所示:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy1MoBtENHBhYLgwP5Hw/
xRGaBPHonApChBPBYD6fiq/QoLXARmyMoOjXHsKrrwysYIujXADM2LZ0MlFvPbBu
lvciWnZwp9CUQPwsZ8xnmBWlHyruxTxNSvV+E/6+2gMOn3I4bmOSIaLx2Y7nCuae
nREvD7Mn0vgFnP7yaN8/9va4q8LoMKlceE5fSYl2QIfC5ZxUtkblbycEWZHLVOkv
+4Iz0ibD8KGo0PaiZl0jmn9yYXFy747xmwVun+Z4czO8Nu+OOVxsQF4hu1pKvTUx
9yHH/vk5Wr0I09VFyt3BT/RkecJbAAWB9/e572T+hhmmJ08wCs29oFa2Cdik9yyE
2QIDAQAB
-----END PUBLIC KEY-----

很明显,两个公钥格式不同。

OpenSSL密钥始终以"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA"开头。

我的密钥以"MIIBKwIBAAKCAQEA"开头。


公钥总是不同的,因为它基于新生成的随机pq - Artjom B.
2个回答

82

很遗憾,你所提到的答案中的代码并不正确 - 它以 私有 键 PEM 格式导出,但是只有公钥字段被正确设置,这与导出标准格式的 RSA 公钥不同。

实际上我写了 其他答案 中的代码,并且当时编写了一个在标准格式下导出公钥的模式,但是由于不需要,所以没有包含在那个答案中。这里是正确的代码:

private static void ExportPublicKey(RSACryptoServiceProvider csp, TextWriter outputStream)
{
    var parameters = csp.ExportParameters(false);
    using (var stream = new MemoryStream())
    {
        var writer = new BinaryWriter(stream);
        writer.Write((byte)0x30); // SEQUENCE
        using (var innerStream = new MemoryStream())
        {
            var innerWriter = new BinaryWriter(innerStream);
            innerWriter.Write((byte)0x30); // SEQUENCE
            EncodeLength(innerWriter, 13);
            innerWriter.Write((byte)0x06); // OBJECT IDENTIFIER
            var rsaEncryptionOid = new byte[] { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 };
            EncodeLength(innerWriter, rsaEncryptionOid.Length);
            innerWriter.Write(rsaEncryptionOid);
            innerWriter.Write((byte)0x05); // NULL
            EncodeLength(innerWriter, 0);
            innerWriter.Write((byte)0x03); // BIT STRING
            using (var bitStringStream = new MemoryStream())
            {
                var bitStringWriter = new BinaryWriter(bitStringStream);
                bitStringWriter.Write((byte)0x00); // # of unused bits
                bitStringWriter.Write((byte)0x30); // SEQUENCE
                using (var paramsStream = new MemoryStream())
                {
                    var paramsWriter = new BinaryWriter(paramsStream);
                    EncodeIntegerBigEndian(paramsWriter, parameters.Modulus); // Modulus
                    EncodeIntegerBigEndian(paramsWriter, parameters.Exponent); // Exponent
                    var paramsLength = (int)paramsStream.Length;
                    EncodeLength(bitStringWriter, paramsLength);
                    bitStringWriter.Write(paramsStream.GetBuffer(), 0, paramsLength);
                }
                var bitStringLength = (int)bitStringStream.Length;
                EncodeLength(innerWriter, bitStringLength);
                innerWriter.Write(bitStringStream.GetBuffer(), 0, bitStringLength);
            }
            var length = (int)innerStream.Length;
            EncodeLength(writer, length);
            writer.Write(innerStream.GetBuffer(), 0, length);
        }

        var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray();
        outputStream.WriteLine("-----BEGIN PUBLIC KEY-----");
        for (var i = 0; i < base64.Length; i += 64)
        {
            outputStream.WriteLine(base64, i, Math.Min(64, base64.Length - i));
        }
        outputStream.WriteLine("-----END PUBLIC KEY-----");
    }
}

private static void EncodeLength(BinaryWriter stream, int length)
{
    if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be non-negative");
    if (length < 0x80)
    {
        // Short form
        stream.Write((byte)length);
    }
    else
    {
        // Long form
        var temp = length;
        var bytesRequired = 0;
        while (temp > 0)
        {
            temp >>= 8;
            bytesRequired++;
        }
        stream.Write((byte)(bytesRequired | 0x80));
        for (var i = bytesRequired - 1; i >= 0; i--)
        {
            stream.Write((byte)(length >> (8 * i) & 0xff));
        }
    }
}

private static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true)
{
    stream.Write((byte)0x02); // INTEGER
    var prefixZeros = 0;
    for (var i = 0; i < value.Length; i++)
    {
        if (value[i] != 0) break;
        prefixZeros++;
    }
    if (value.Length - prefixZeros == 0)
    {
        EncodeLength(stream, 1);
        stream.Write((byte)0);
    }
    else
    {
        if (forceUnsigned && value[prefixZeros] > 0x7f)
        {
            // Add a prefix zero to force unsigned if the MSB is 1
            EncodeLength(stream, value.Length - prefixZeros + 1);
            stream.Write((byte)0);
        }
        else
        {
            EncodeLength(stream, value.Length - prefixZeros);
        }
        for (var i = prefixZeros; i < value.Length; i++)
        {
            stream.Write(value[i]);
        }
    }
}

1
你知道吗?你的实现实际上被用在Azure Powershell中了。链接 - foldone
3
@foldone - 我现在知道了!谢谢你告诉我! - Iridium
这个解决方案完美地运作。唯一的缺陷是我不能很好地理解流程,是否有解释或深入分析以更好地理解这个流程以及为什么要这样做? - pampua84
建议对 BinaryWriter 实例进行 Dispose 处理,因为它们是 IDisposable 接口的实现类。 - Neil Moss

0
在 .Net Framework 上,从一个 RSA 对象中提取公钥的一种简单方法是创建一个临时的 X509Certificate2 对象,并在其上调用 GetPublicKey() 方法,如下所示:
var tempCertRequest = new CertificateRequest("CN=DummyCN", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
var tempCert = tempCertRequest.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddDays(3));
return tempCert.GetPublicKey();

GetPublicKey() 将返回公钥作为 byte[]。如果需要,您可以使用 Convert.ToBase64String() 将其转换为 Base64。


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