zoukankan      html  css  js  c++  java
  • .NET导入openssl生成的公钥之BEGIN RSA PUBLIC KEY

    .NET导入openssl生成的公钥之BEGIN RSA PUBLIC KEY  

     

    我得到了一个公钥,形式如下

    -----BEGIN RSA PUBLIC KEY-----

    MIGJAoGBAMroxz3qtok…….

    ……

    -----END RSA PUBLIC KEY-----

    相要用C#程序,将它导入并加密数据传给opensll应用程序解密。在网上找到很多方法,其中opensslkey.cs文件的实现最完善,但它只能解析-----BEGIN PUBLIC KEY-----打头的公钥。而且内容的长度也不同,看来它是解不开了。在搜索的过程中,发现Jeffrey Walton有文章Cryptographic Interoperability: Keys,讲的是密钥的编解码。从以下地址可以获得

    http://www.codeproject.com/KB/security/CryptoInteropKeys.aspx

    多从他附带的代码中,搞清楚了密钥的格式。分析了一下我的钥格式,发现其实公钥格式很简单。如下:

    //* +- SEQUENCE        // RSAPrivateKey 
    //*   +- INTEGER(N)   // N 
    //*   +- INTEGER(E)   // E

        上面我们看到的就是这个公钥中的全部内容了,比-----BEGIN PUBLIC KEY-----打头的要少很多内容。就是因为太简单了,才给我造成了很大的麻烦!上面的格式其实是一个ASN标准编码,Jeffrey Walton提供的源码中有一个类AsnParser很容易就能解析出来。于是,我在Jeffrey Walton写的类AsnKeyParser中加了一个函数ParsePkcsRSAPublicKey。

    internal RSAParameters ParsePkcsRSAPublicKey() 

        //* +- SEQUENCE        // RSAPrivateKey 
        //*   +- INTEGER(N)   // N 
        //*   +- INTEGER(E)   // E

        RSAParameters parameters = new RSAParameters();

        // Sanity Check 
        int length = 0;

        // Checkpoint 
        int position = parser.CurrentPosition();

        // Ignore Sequence - PublicKeyInfo 
        length = parser.NextSequence(); 
        if (length != parser.RemainingBytes()) 
        { 
            StringBuilder sb = new StringBuilder("Incorrect Sequence Size. "); 
            sb.AppendFormat("Specified: {0}, Remaining: {1}", 
              length.ToString(CultureInfo.InvariantCulture), 
              parser.RemainingBytes().ToString(CultureInfo.InvariantCulture)); 
            throw new BerDecodeException(sb.ToString(), position); 
        }

        // Checkpoint 
        position = parser.CurrentPosition(); 
        int remaining = parser.RemainingBytes();

        parameters.Modulus = TrimLeadingZero(parser.NextInteger());

        parameters.Exponent = TrimLeadingZero(parser.NextInteger());

        Debug.Assert(0 == parser.RemainingBytes());

        return parameters; 
    }

    注意加红的两行就是公钥的全部。

        由于AsnKeyParser只有一个构造函数,并且只接受文件名,并从文件中读取数据。但它并不认识PEM格式,所以PEM的解析还是放在外面。看一下原来的构造函数:

    internal AsnKeyParser(String pathname) 

        using (BinaryReader reader = new BinaryReader( 
            new FileStream(pathname, FileMode.Open, FileAccess.Read))) 
        { 
            FileInfo info = new FileInfo(pathname);

            parser = new AsnParser(reader.ReadBytes((int)info.Length)); 
        } 
    }

    可以看出来,从文件读取数据之后,直接把二进制数据传给了AsnParser。而AsnParser当然不能解析Base64解码后的数据。我又给它加了一个构造函数

    internal AsnKeyParser(AsnParser parser) 

        this.parser = parser; 
    }

    看出来了吧,就是要在外面做完Base64解码之后再传给AsnKeyParser。

        最后再写了一个从文件加载,及调用AsnKeyParser的函数,就大功告成了!

    private static RSAParameters LoadRsaPublicKey() 

        string s = File.ReadAllText("pub.pem"); // 从文件读取 
        StringBuilder build = new StringBuilder(s);

        // 去掉头尾

        build.Replace("-----BEGIN RSA PUBLIC KEY-----", ""); 
        build.Replace("-----END RSA PUBLIC KEY-----", "");

        s = build.ToString().Trim(); 
        byte[] binKey = System.Convert.FromBase64String(s);  // Base64解码

        AsnParser parser = new AsnParser(binKey); // 现在已经是AsnParser能认识的数据了 
        AsnKeyParser keyParser = new AsnKeyParser(parser);  // 就刚加的构造孙数

        RSAParameters publicKey = keyParser.ParsePkcsRSAPublicKey();  // 还是用刚加的解析函数

        return publicKey; // 公钥已经得到,可以尽情的用RSACryptoServiceProvider了。 
    }

        为了测试得到的密钥正不正常,我用openssl生成了一对密钥。再用我的程序来加载密钥,并测试加密与解密,来最后解密出来的结果,是否与加密前一至。

    生成的密钥如下你也可以自己生成

    pub.pem

    -----BEGIN RSA PUBLIC KEY----- 
    MIGJAoGBAMroxz3qtok9aa777ssNfVKHgGI8BPrGexhS2PE+2xZGffakR2QbS5vw 
    CidhVzrpzRJJuaZqktBrcVC7as1TsP2mY8RgWPNOvHisDDZp+H5c2+UwVQ6bV1tk 
    MXx1RSDryOO4mmeONJE8aJcGG+9KWkoZEQL5XIzrzy3NeYNYu5J1AgMBAAE= 
    -----END RSA PUBLIC KEY-----

    pri.pem

    -----BEGIN RSA PRIVATE KEY----- 
    MIICXgIBAAKBgQDK6Mc96raJPWmu++7LDX1Sh4BiPAT6xnsYUtjxPtsWRn32pEdk 
    G0ub8AonYVc66c0SSbmmapLQa3FQu2rNU7D9pmPEYFjzTrx4rAw2afh+XNvlMFUO 
    m1dbZDF8dUUg68jjuJpnjjSRPGiXBhvvSlpKGREC+VyM688tzXmDWLuSdQIDAQAB 
    AoGBAMfr6sO6yvcVp1ddqr4uIFh8YaZodI+RmB8zIcUwpTShZ+Lnod+kdS7Dp319 
    jzDgw8lNErpBLz5jXlapEmYUG8FNOLK/z45oVVSlLZquuQowcR3JoDtb/yKvOPdQ 
    EavCsvoQT7lIn4oCUAWZP/yyQQA2TDjyVmUF9gQctbbuwPkBAkEA9L0FC91Pa3dd 
    Ry1sD0rhcrLAsFZX0gzd3ozgAQGM/p2dY1AN0pOF15mJgaHRP2UImqb0qtmsroSd 
    BwEsZulwcQJBANQ/BMFnfcvxh7IvrxvA8Mh/Edb8RJcKxuutLjABj4Ah8nIdGb5S 
    XHhCQ3JIA2x6ydygY6ldqLvsYAQiuOY2hEUCQQDlV3QxKBTSmiq5FqGauwsFlujm 
    1iK53gDUGqOXjcJ4n27rsAsj98aGwYSQC/mwNJeZhTbmG9GsQO19sOXREpShAkEA 
    sKx4b+mO3GoEE33/3DFh/PNRTUyWZ8hPxzRUIx/ZbMZVQ0oX+MYkNPKrpABv4Sfg 
    ymc0LnJJF4zua+LfWLp+pQJAXFa8xvDdLcQ4PhG4pDqUuvklbUkyl36GrfU9CkIK 
    GbnoDXw7W5SJ0qb258JxIx4cNsDIC+CU0r7Ejmo5g3RMew== 
    -----END RSA PRIVATE KEY-----

    把它们放在binDebug目录下

    又实现了一个解析密钥的函数

    internal RSAParameters ParsePkcsRSAPrivateKey() 

        //*+- SEQUENCE            // RSAPrivateKey 
        //*     +- INTEGER(0)       // Version - 0 (v1998) 
        //*     +- INTEGER(N) 
        //*     +- INTEGER(E) 
        //*     +- INTEGER(D) 
        //*     +- INTEGER(P) 
        //*     +- INTEGER(Q) 
        //*     +- INTEGER(DP) 
        //*     +- INTEGER(DQ) 
        //*     +- INTEGER(Inv Q)

        RSAParameters parameters = new RSAParameters();

        // Current value 
        byte[] value = null;

        // Checkpoint 
        int position = parser.CurrentPosition();

        // Sanity Check 
        int length = 0;

        // Ignore Sequence - PrivateKeyInfo 
        length = parser.NextSequence(); 
        if (length != parser.RemainingBytes()) 
        { 
            StringBuilder sb = new StringBuilder("Incorrect Sequence Size. "); 
            sb.AppendFormat("Specified: {0}, Remaining: {1}", 
              length.ToString(CultureInfo.InvariantCulture), parser.RemainingBytes().ToString(CultureInfo.InvariantCulture)); 
            throw new BerDecodeException(sb.ToString(), position); 
        }

        // Checkpoint 
        position = parser.CurrentPosition(); 
        // Version 
        value = parser.NextInteger(); 
        if (0x00 != value[0]) 
        { 
            StringBuilder sb = new StringBuilder("Incorrect RSAPrivateKey Version. "); 
            BigInteger v = new BigInteger(value); 
            sb.AppendFormat("Expected: 0, Specified: {0}", v.ToString(10)); 
            throw new BerDecodeException(sb.ToString(), position); 
        }

        parameters.Modulus = TrimLeadingZero(parser.NextInteger()); 
        parameters.Exponent = TrimLeadingZero(parser.NextInteger()); 
        parameters.D = TrimLeadingZero(parser.NextInteger()); 
        parameters.P = TrimLeadingZero(parser.NextInteger()); 
        parameters.Q = TrimLeadingZero(parser.NextInteger()); 
        parameters.DP = TrimLeadingZero(parser.NextInteger()); 
        parameters.DQ = TrimLeadingZero(parser.NextInteger()); 
        parameters.InverseQ = TrimLeadingZero(parser.NextInteger());

        Debug.Assert(0 == parser.RemainingBytes());

        return parameters; 
    }

    密钥比公钥复杂的多,但也是通常所见到的格式的一小部份。

        加载密钥的函数

    private static RSAParameters LoadRsaPrivateKey() 

        string s = File.ReadAllText("pri.pem"); 
        StringBuilder build = new StringBuilder(s);

        build.Replace("-----BEGIN RSA PRIVATE KEY-----", ""); 
        build.Replace("-----END RSA PRIVATE KEY-----", "");

        s = build.ToString().Trim(); 
        byte[] binKey = Convert.FromBase64String(s);

        AsnParser parser = new AsnParser(binKey); 
        AsnKeyParser keyParser = new AsnKeyParser(parser);

        RSAParameters privateKey = keyParser.ParsePkcsRSAPrivateKey();

        return privateKey; 
    }

    与加载公钥差不多

    最后还有一点测试代码

    private static void TestRsaKeys() // 测试的入口 

        RSAParameters publicKey = LoadRsaPublicKey(); 
        RSAParameters privateKey = LoadRsaPrivateKey();

        string s = TryEncrypt(publicKey); // 测试加密 
        System.Console.Out.WriteLine(s);

        s = TryDecrypt(privateKey, s); // 测试解密 
        System.Console.Out.WriteLine(s); 
    }

    private static string TryEncrypt(RSAParameters publicKey) 

        RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); 
        rsa.ImportParameters(publicKey);

        string test = "用来测试加密的串"; 
        byte[] bytes = Encoding.Unicode.GetBytes(test); 
        byte[] encryptedBytes = rsa.Encrypt(bytes, false);

        string outString = Convert.ToBase64String(encryptedBytes);

        return outString; 
    }

    private static string TryDecrypt(RSAParameters privateKey, string src) 

        RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); 
        rsa.ImportParameters(privateKey);

        byte[] bytes = Convert.FromBase64String(src); 
        byte[] decryptBytes = rsa.Decrypt(bytes, false);

        string outString = Encoding.Unicode.GetString(decryptBytes);

        return outString; 
    }

    经过测试已经证明我的解码是成功的。感谢Jeffrey Walton!

    值得一提的是AsnKeyParser中的ParseRSAPublicKey是可以直接解析-----BEGIN PUBLIC KEY-----打头的公钥。通常openssl生成的私钥都是-----BEGIN RSA PRIVATE KEY----- 打头的,所以还是要用我写的ParsePkcsRSAPrivateKey函数。

  • 相关阅读:
    router基本使用
    函数声明 和 var声明的优先级
    适用于Windows桌面应用程序的.NET Core 3
    在.Net Core 3.0中尝试新的System.Text.Json API
    在WPF中使用.NET Core 3.0依赖项注入和服务提供程序
    WPF控件获得焦点时去除虚线框
    Call asynchronous method in constructor
    将自定义控件加载到RichTextbox并进行交互
    WPF应用无法使用Snoop分析的解决办法
    关于序列化和反序列化
  • 原文地址:https://www.cnblogs.com/adylee/p/3611491.html
Copyright © 2011-2022 走看看