.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-----

把它們放在bin\Debug目錄下

 

又實現了一個解析密鑰的函數

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函數。

發佈了13 篇原創文章 · 獲贊 3 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章