最近因考虑接口安全问题,有实现给WEB API实现统一的参数鉴权功能,以防止请求参数被篡改或重复执行,参数鉴权方法基本与常见的鉴权思路相同,采用(timestamp+sign),而我为了防止timestamp被更改,sign算法(timestamp+相关参数排序、格式化后拼接再MD5)也因为在前端是不安全的,故对timestamp采取使用非对称加解密,以尽可能的保证生成的sign不易被破解或替换;
RSA加解密(即:非对称加解密)
生成公钥、私钥对方法(C#),生成出来后默认都是XML格式:
1
2
3
4
5
6
7
8
9
10
|
public static Tuple< string , string > GeneratePublicAndPrivateKeyPair() { using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) { string publicKey = rsa.ToXmlString( false ); // 公钥 string privateKey = rsa.ToXmlString( true ); // 私钥 return Tuple.Create(publicKey, privateKey); } } |
使用公钥加密:(支持分段加密,普通单次加密可能会因为内容过长而报错)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public static string RSAEncrypt( string publicKey, string rawInput) { if ( string .IsNullOrEmpty(rawInput)) { return string .Empty; } if ( string .IsNullOrWhiteSpace(publicKey)) { throw new ArgumentException( "Invalid Public Key" ); } using (var rsaProvider = new RSACryptoServiceProvider()) { var inputBytes = Encoding.UTF8.GetBytes(rawInput); //有含义的字符串转化为字节流 rsaProvider.FromXmlString(publicKey); //载入公钥 int bufferSize = (rsaProvider.KeySize / 8) - 11; //单块最大长度 var buffer = new byte [bufferSize]; using (MemoryStream inputStream = new MemoryStream(inputBytes), outputStream = new MemoryStream()) { while ( true ) { //分段加密 int readSize = inputStream.Read(buffer, 0, bufferSize); if (readSize <= 0) { break ; } var temp = new byte [readSize]; Array.Copy(buffer, 0, temp, 0, readSize); var encryptedBytes = rsaProvider.Encrypt(temp, false ); outputStream.Write(encryptedBytes, 0, encryptedBytes.Length); } return Convert.ToBase64String(outputStream.ToArray()); //转化为字节流方便传输 } } } |
使用私钥解密:(支持分段解密,普通单次解密可能会因为密文过长而报错)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public static string RSADecrypt( string privateKey, string encryptedInput) { if ( string .IsNullOrEmpty(encryptedInput)) { return string .Empty; } if ( string .IsNullOrWhiteSpace(privateKey)) { throw new ArgumentException( "Invalid Private Key" ); } using (var rsaProvider = new RSACryptoServiceProvider()) { var inputBytes = Convert.FromBase64String(encryptedInput); rsaProvider.FromXmlString(privateKey); int bufferSize = rsaProvider.KeySize / 8; var buffer = new byte [bufferSize]; using (MemoryStream inputStream = new MemoryStream(inputBytes), outputStream = new MemoryStream()) { while ( true ) { int readSize = inputStream.Read(buffer, 0, bufferSize); if (readSize <= 0) { break ; } var temp = new byte [readSize]; Array.Copy(buffer, 0, temp, 0, readSize); var rawBytes = rsaProvider.Decrypt(temp, false ); outputStream.Write(rawBytes, 0, rawBytes.Length); } return Encoding.UTF8.GetString(outputStream.ToArray()); } } } |
如果都是C#项目可能如上两个方法就可以了,但如果需要与WEB前端、JAVA等其它编程语言协同交互处理时(比如:WEB前端用公钥加密,后端C#私钥解密),则可能因为公钥与私钥的格式不相同而导致无法正常的进行对接【前端、JAVA 等语言使用的是PEM格式的,而C#使用的是XML格式】,网上查XML转PEM格式方案时,都是复制自:https://www.cnblogs.com/micenote/p/7862989.html 这篇文章,但其实这篇文章也只是写了私钥XML转PEM格式,并没有说明公钥XML如何转PEM格式,而且只写了支持从文件中获取内容再转换,方案不全,但是给了我思路,我经过各种验证,最终实现了比较友好的PEM与XML格式的相互转换方式,且经过单元测试验证通过,在此分享给大家。
如下是完整的XML与PEM格式转换器类代码;(注意需引入BouncyCastle nuget包)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; namespace Zuowj.Common { /// <summary> /// RSA公钥、私钥对格式(XML与PEM)转换器 /// author:zuowenjun /// date:2020-12-29 /// </summary> public static class RsaKeysFormatConverter { /// <summary> /// XML公钥转成Pem公钥 /// </summary> /// <param name="xmlPublicKey"></param> /// <returns></returns> public static string XmlPublicKeyToPem( string xmlPublicKey) { RSAParameters rsaParam; using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) { rsa.FromXmlString(xmlPublicKey); rsaParam = rsa.ExportParameters( false ); } RsaKeyParameters param = new RsaKeyParameters( false , new BigInteger(1, rsaParam.Modulus), new BigInteger(1, rsaParam.Exponent)); string pemPublicKeyStr = null ; using (var ms = new MemoryStream()) { using (var sw = new StreamWriter(ms)) { var pemWriter = new Org.BouncyCastle.OpenSsl.PemWriter(sw); pemWriter.WriteObject(param); sw.Flush(); byte [] buffer = new byte [ms.Length]; ms.Position = 0; ms.Read(buffer, 0, ( int )ms.Length); pemPublicKeyStr = Encoding.UTF8.GetString(buffer); } } return pemPublicKeyStr; } /// <summary> /// Pem公钥转成XML公钥 /// </summary> /// <param name="pemPublicKeyStr"></param> /// <returns></returns> public static string PemPublicKeyToXml( string pemPublicKeyStr) { RsaKeyParameters pemPublicKey; using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(pemPublicKeyStr))) { using (var sr = new StreamReader(ms)) { var pemReader = new Org.BouncyCastle.OpenSsl.PemReader(sr); pemPublicKey = (RsaKeyParameters)pemReader.ReadObject(); } } var p = new RSAParameters { Modulus = pemPublicKey.Modulus.ToByteArrayUnsigned(), Exponent = pemPublicKey.Exponent.ToByteArrayUnsigned() }; string xmlPublicKeyStr; using (var rsa = new RSACryptoServiceProvider()) { rsa.ImportParameters(p); xmlPublicKeyStr = rsa.ToXmlString( false ); } return xmlPublicKeyStr; } /// <summary> /// XML私钥转成PEM私钥 /// </summary> /// <param name="xmlPrivateKey"></param> /// <returns></returns> public static string XmlPrivateKeyToPem( string xmlPrivateKey) { RSAParameters rsaParam; using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) { rsa.FromXmlString(xmlPrivateKey); rsaParam = rsa.ExportParameters( true ); } var param = new RsaPrivateCrtKeyParameters( new BigInteger(1, rsaParam.Modulus), new BigInteger(1, rsaParam.Exponent), new BigInteger(1, rsaParam.D), new BigInteger(1, rsaParam.P), new BigInteger(1, rsaParam.Q), new BigInteger(1, rsaParam.DP), new BigInteger(1, rsaParam.DQ), new BigInteger(1, rsaParam.InverseQ)); string pemPrivateKeyStr = null ; using (var ms = new MemoryStream()) { using (var sw = new StreamWriter(ms)) { var pemWriter = new Org.BouncyCastle.OpenSsl.PemWriter(sw); pemWriter.WriteObject(param); sw.Flush(); byte [] buffer = new byte [ms.Length]; ms.Position = 0; ms.Read(buffer, 0, ( int )ms.Length); pemPrivateKeyStr = Encoding.UTF8.GetString(buffer); } } return pemPrivateKeyStr; } /// <summary> /// Pem私钥转成XML私钥 /// </summary> /// <param name="pemPrivateKeyStr"></param> /// <returns></returns> public static string PemPrivateKeyToXml( string pemPrivateKeyStr) { RsaPrivateCrtKeyParameters pemPrivateKey; using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(pemPrivateKeyStr))) { using (var sr = new StreamReader(ms)) { var pemReader = new Org.BouncyCastle.OpenSsl.PemReader(sr); var keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject(); pemPrivateKey = (RsaPrivateCrtKeyParameters)keyPair.Private; } } var p = new RSAParameters { Modulus = pemPrivateKey.Modulus.ToByteArrayUnsigned(), Exponent = pemPrivateKey.PublicExponent.ToByteArrayUnsigned(), D = pemPrivateKey.Exponent.ToByteArrayUnsigned(), P = pemPrivateKey.P.ToByteArrayUnsigned(), Q = pemPrivateKey.Q.ToByteArrayUnsigned(), DP = pemPrivateKey.DP.ToByteArrayUnsigned(), DQ = pemPrivateKey.DQ.ToByteArrayUnsigned(), InverseQ = pemPrivateKey.QInv.ToByteArrayUnsigned(), }; string xmlPrivateKeyStr; using (var rsa = new RSACryptoServiceProvider()) { rsa.ImportParameters(p); xmlPrivateKeyStr = rsa.ToXmlString( true ); } return xmlPrivateKeyStr; } } } |
如下是单元测试代码:
1
2
3
4
5
|
//公钥(XML、PEM格式互)测试 string srcPublicKey = “具体的XML Public Key”; string pemPublicKeyStr= RsaKeysFormatConverter.XmlPublicKeyToPem(publicKey); string xmlPublicKeyStr= RsaKeysFormatConverter.PemPublicKeyToXml(pemPublicKeyStr); Assert.AreEqual(srcPublicKey, xmlPublicKeyStr); |
1
2
3
4
5
|
//私钥(XML、PEM格式互)测试 string srcPrivateKey = “具体的XML Private Key”; string pemPrivateKeyStr = RsaKeysFormatConverter.XmlPrivateKeyToPem(srcPrivateKey); string xmlPrivateKeyStr = RsaKeysFormatConverter.PemPrivateKeyToXml(pemPrivateKeyStr); Assert.AreEqual(privateKey,xmlPrivateKeyStr) |
当然也可以不用这么费劲自己实现格式转换,可以使用在线网站直接转换:https://the-x.cn/certificate/XmlToPem.aspx ,另外也有一篇文章实现了类似的功能,但生成的PEM格式并非完整的格式,缺少注释头尾:https://www.cnblogs.com/datous/p/RSAKeyConvert.html
以上就是c# RSA非对称加解密及XML&PEM格式互换方案的详细内容,更多关于c# RSA非对称加解密的资料请关注服务器之家其它相关文章!
原文链接:https://www.cnblogs.com/zuowj/archive/2020/12/29/14207740.html