• .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函数。

  • 相关阅读:
    小波变换的引入,通俗易懂
    Leetcode 437. Path Sum III
    Leetcode 113. Path Sum II
    Leetcode 112 Path Sum
    Leetcode 520 Detect Capital
    Leetcode 443 String Compression
    Leetcode 38 Count and Say
    python中的生成器(generator)总结
    python的random模块及加权随机算法的python实现
    leetcode 24. Swap Nodes in Pairs(链表)
  • 原文地址:https://www.cnblogs.com/adylee/p/3611491.html
Copyright © 2020-2023  润新知