我獲得了一個公鑰,形式以下ide
-----BEGIN RSA PUBLIC KEY-----函數
MIGJAoGBAMroxz3qtok…….測試
……ui
-----END RSA PUBLIC KEY-----this
相要用C#程序,將它導入並加密數據傳給opensll應用程序解密。在網上找到不少方法,其中opensslkey.cs文件的實現最完善,但它只能解析-----BEGIN PUBLIC KEY-----打頭的公鑰。並且內容的長度也不一樣,看來它是解不開了。在搜索的過程當中,發現Jeffrey Walton有文章Cryptographic Interoperability: Keys,講的是密鑰的編解碼。從如下地址能夠得到編碼
http://www.codeproject.com/KB/security/CryptoInteropKeys.aspx加密
多從他附帶的代碼中,搞清楚了密鑰的格式。分析了一下個人鑰格式,發現其實公鑰格式很簡單。以下:spa
//* +- SEQUENCE // RSAPrivateKey
//* +- INTEGER(N) // N
//* +- INTEGER(E) // E3d
上面咱們看到的就是這個公鑰中的所有內容了,比-----BEGIN PUBLIC KEY-----打頭的要少不少內容。就是由於太簡單了,纔給我形成了很大的麻煩!上面的格式實際上是一個ASN標準編碼,Jeffrey Walton提供的源碼中有一個類AsnParser很容易就能解析出來。因而,我在Jeffrey Walton寫的類AsnKeyParser中加了一個函數ParsePkcsRSAPublicKey。code
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函數。