Web应用中往往涉及到敏感的数据,由于HTTP协议以明文的形式与服务器进行交互,因此可以通过截获请求的数据包进行分析来盗取有用的信息。虽然https可以对传输的数据进行加密,但是必须要申请证书(一般都是收费的),成本较高。那么问题来了,如果对web提交的敏感数据进行加密呢?web应用中,前端的数据处理和交互基本上都是靠javascript来完成,后台的逻辑处理可以C#(java)等进行处理。
微软的C#中虽然有RSA算法,但是格式和OpenSSL生成的公钥/私钥文件格式并不兼容。这个也给贯通前后台的RSA加密解密带来了难度。为了兼容OpenSSL生成的公钥/私钥文件格式,贯通javascript和C#的RSA加密解密算法,必须对C#内置的方法进行再度封装。
下面以登录为例,用户在密码框输入密码后,javascript发送ajax请求时,对密码先进行rsa加密后再发送,服务器接收到加密后的密码后,先对其进行解密, 然后再验证登录是否成功。
1 为了进行RSA加密解密,首先需要用openssl生成一对公钥和私钥(没有的先下载openssl):
1) 打开openssl.exe文件,输入 genrsa -out openssl_rsa_priv.pem 1024
此命令在openssl.exe同目录下生成openssl_rsa_private_key.pem文件。
2) 生成公钥 rsa -in openssl_rsa__private.pem -pubout -out openssl_rsa__public.pem
以上命令会创建如下的文件:
这个文件可以用文本编辑器进行打开,查看内容。
-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0w036ClSD0LvxPROMun0u022R OJlZE6P3m+gjq3gpi4n7lo8jhTqMqgccDbVJqnIfMzWS9O3lnlQXWTxJ3B4XJ52F AcriY5brOXUVgBLx5QMHLLd1gtJnmG4i7r4ytgX7XVKRnojR6zca1YnS0lbGGDF1 CGllB1riNrdksSQP+wIDAQAB -----END PUBLIC KEY-----
-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQC0w036ClSD0LvxPROMun0u022ROJlZE6P3m+gjq3gpi4n7lo8j hTqMqgccDbVJqnIfMzWS9O3lnlQXWTxJ3B4XJ52FAcriY5brOXUVgBLx5QMHLLd1 gtJnmG4i7r4ytgX7XVKRnojR6zca1YnS0lbGGDF1CGllB1riNrdksSQP+wIDAQAB AoGAIOyl6lIxXKULZoBKbEqXfIz0GwxlGg1ywyn5mW2lAGQzKMken0ioBnD9xIVW rOlHyhkIvBCyuC0jgfE2Avn93MlB3j0WRuXMFlJpCBlEklMilO9Zgmwl+vTB3VZb 8VzdrEEEUBio7LWP/KvSo+IFlNjDTKgAczbLTwAmj4w6g0ECQQDm4yxPdxcU2ywZ
7PyjIMM9qnSah9KcrjU8gjEyHsUpgTjhw1cx7Peo+vRiHqxDy1yaSu1BlwRR52pC jKNnl0QhAkEAyGx3NxEIiLk2oXGGbIMZ4P6geC8gYu01BiRNWVf0Yi7+sCH68eUP oI+G5bJ8bvzXpvHjQi0s2OlRfct/qtPQmwJBALa+2DONbxdy4lUi3lO/esk0QVaO aoTY3gomggnJkQRo4zzOABXkGaIF/6gp3u9J5uG4rFFd1m19XP2Pk0ZK1AECQBYi
lJAKW4zuF7CA3z3AxOzqckKTwdnrJL4G6FwDsMPfONWvCw4IJE+xSk64BbIkTpTr hhPa9WcHba6c+P6e4h0CQQDWeGMMpkqPG/w4afNCGmvRnM8vNkGUAmDGvCsfkTID
ijpKl5SD55hPHsWE5rsv1TLUpkWtrFBcg61bHwMUP3cv -----END RSA PRIVATE KEY-----
2 用jsencrypt对密码进行加密:
首先需要导入js包文件
<script src="dist/js/jsencrypt.js"></script>
1 var encrypt = new JSEncrypt(); 2 var pubkey = "-----BEGIN PUBLIC KEY----- \ 3 MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAj0dPnBMf3Z4VT1B8Ee6bjKNs \ 4 hlYj7xvGijAa8RCdmGR7mrtrExnk8mdUlwdcS05gc4SSFOyWJcYtKUHpWn8/pkS0 \ 5 vgGOl9Bzn0Xt9hiqTb3pZAfykNrMDGZMgJgfD6KTnfzVUAOupvxjcGkcoj6/vV5I \ 6 eMcx8mT/z3elfsDSjQIDAQAB \ 7 -----END PUBLIC KEY-----"; 8 encrypt.setPublicKey(pubkey); 9 var encrypted = encrypt.encrypt($('#txtpwd').val()); 10 //console.log(encrypted);
11 $.ajax({ 12 type: "POST", 13 url: "http://localhost:24830/services/rsa_pem.ashx", 14 data: { "pwd": encrypted }, 15 dataType: "Json", 16 error: function (xhr, status, error) { 17 // alert(error);
18 $("#txtInfo").text(' 请求服务器失败!'); 19 $(that).text('登 录'); 20 $(that).attr('disabled', false); 21 }, 22 success: function (json) { 23
24 if (uid == "admin" && json.data=="000") { 25 window.location.href = "index.html"; 26 } 27 else { 28 $("#txtInfo").text(' 用户名或者密码错误!'); 29 $(that).text('登 录'); 30 $(that).attr('disabled', false); 31 } 32 } 33 });
3 后台用C#进行解密
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Linq; 5 using System.Security.Cryptography; 6 using System.Text; 7 using System.Threading.Tasks; 8
9 namespace CMCloud.SaaS 10 { 11 public class RSACryptoService 12 { 13 private RSACryptoServiceProvider _privateKeyRsaProvider; 14 private RSACryptoServiceProvider _publicKeyRsaProvider; 15
16 /// <summary>
17 /// RSA解密 18 /// </summary>
19 /// <param name="cipherText"></param>
20 /// <returns></returns>
21 public string Decrypt(string cipherText) 22 { 23 if (_privateKeyRsaProvider == null) 24 { 25 throw new Exception("_privateKeyRsaProvider is null"); 26 } 27 return Decrypt2(cipherText); 28 } 29 /// <summary>
30 /// RSA加密 31 /// </summary>
32 /// <param name="text"></param>
33 /// <returns></returns>
34 public string Encrypt(string text) 35 { 36 if (_publicKeyRsaProvider == null) 37 { 38 throw new Exception("_publicKeyRsaProvider is null"); 39 } 40 return Encrypt2(text); 41 //return Convert.ToBase64String(_publicKeyRsaProvider.Encrypt(Encoding.UTF8.GetBytes(text), false));
42
43 } 44 private string Encrypt2(string text) 45 { 46
47
48 Byte[] PlaintextData = Encoding.UTF8.GetBytes(text); 49 int MaxBlockSize = _publicKeyRsaProvider.KeySize / 8 - 11;//加密块最大长度限制
50
51 if (PlaintextData.Length <= MaxBlockSize) 52 { 53 return Convert.ToBase64String(_publicKeyRsaProvider.Encrypt(PlaintextData, false)); 54 } 55 else
56 { 57 using (MemoryStream PlaiStream = new MemoryStream(PlaintextData)) 58 using (MemoryStream CrypStream = new MemoryStream()) 59 { 60 Byte[] Buffer = new Byte[MaxBlockSize]; 61 int BlockSize = PlaiStream.Read(Buffer, 0, MaxBlockSize); 62
63 while (BlockSize > 0) 64 { 65 Byte[] ToEncrypt = new Byte[BlockSize]; 66 Array.Copy(Buffer, 0, ToEncrypt, 0, BlockSize); 67
68 Byte[] Cryptograph = _publicKeyRsaProvider.Encrypt(ToEncrypt, false); 69 CrypStream.Write(Cryptograph, 0, Cryptograph.Length); 70
71 BlockSize = PlaiStream.Read(Buffer, 0, MaxBlockSize); 72 } 73
74 return Convert.ToBase64String(CrypStream.ToArray(), Base64FormattingOptions.None); 75 } 76 } 77
78
79
80
81 } 82
83 private string Decrypt2(string ciphertext) 84 { 85
86
87 Byte[] CiphertextData = Convert.FromBase64String(ciphertext); 88 int MaxBlockSize = _privateKeyRsaProvider.KeySize / 8; //解密块最大长度限制
89
90 if (CiphertextData.Length <= MaxBlockSize) 91 return System.Text.Encoding.UTF8.GetString(_privateKeyRsaProvider.Decrypt(CiphertextData, false)); 92
93 using (MemoryStream CrypStream = new MemoryStream(CiphertextData)) 94 using (MemoryStream PlaiStream = new MemoryStream()) 95 { 96 Byte[] Buffer = new Byte[MaxBlockSize]; 97 int BlockSize = CrypStream.Read(Buffer, 0, MaxBlockSize); 98
99 while (BlockSize > 0) 100 { 101 Byte[] ToDecrypt = new Byte[BlockSize]; 102 Array.Copy(Buffer, 0, ToDecrypt, 0, BlockSize); 103
104 Byte[] Plaintext = _privateKeyRsaProvider.Decrypt(ToDecrypt, false); 105 PlaiStream.Write(Plaintext, 0, Plaintext.Length); 106
107 BlockSize = CrypStream.Read(Buffer, 0, MaxBlockSize); 108 } 109
110 return System.Text.Encoding.UTF8.GetString(PlaiStream.ToArray()); 111 } 112 } 113 public RSACryptoService(string privateKey, string publicKey = null) 114 { 115 if (!string.IsNullOrEmpty(privateKey)) 116 { 117 _privateKeyRsaProvider = CreateRsaProviderFromPrivateKey(privateKey); 118 } 119
120 if (!string.IsNullOrEmpty(publicKey)) 121 { 122 _publicKeyRsaProvider = CreateRsaProviderFromPublicKey(publicKey); 123 } 124 } 125
126
127
128 private RSACryptoServiceProvider CreateRsaProviderFromPrivateKey(string privateKey) 129 { 130 var privateKeyBits = System.Convert.FromBase64String(privateKey); 131
132 var RSA = new RSACryptoServiceProvider(); 133 var RSAparams = new RSAParameters(); 134
135 using (BinaryReader binr = new BinaryReader(new MemoryStream(privateKeyBits))) 136 { 137 byte bt = 0; 138 ushort twobytes = 0; 139 twobytes = binr.ReadUInt16(); 140 if (twobytes == 0x8130) 141 binr.ReadByte(); 142 else if (twobytes == 0x8230) 143 binr.ReadInt16(); 144 else
145 throw new Exception("Unexpected value read binr.ReadUInt16()"); 146
147 twobytes = binr.ReadUInt16(); 148 if (twobytes != 0x0102) 149 throw new Exception("Unexpected version"); 150
151 bt = binr.ReadByte(); 152 if (bt != 0x00) 153 throw new Exception("Unexpected value read binr.ReadByte()"); 154
155 RSAparams.Modulus = binr.ReadBytes(GetIntegerSize(binr)); 156 RSAparams.Exponent = binr.ReadBytes(GetIntegerSize(binr)); 157 RSAparams.D = binr.ReadBytes(GetIntegerSize(binr)); 158 RSAparams.P = binr.ReadBytes(GetIntegerSize(binr)); 159 RSAparams.Q = binr.ReadBytes(GetIntegerSize(binr)); 160 RSAparams.DP = binr.ReadBytes(GetIntegerSize(binr)); 161 RSAparams.DQ = binr.ReadBytes(GetIntegerSize(binr)); 162 RSAparams.InverseQ = binr.ReadBytes(GetIntegerSize(binr)); 163 } 164
165 RSA.ImportParameters(RSAparams); 166 return RSA; 167 } 168
169 private int GetIntegerSize(BinaryReader binr) 170 { 171 byte bt = 0; 172 byte lowbyte = 0x00; 173 byte highbyte = 0x00; 174 int count = 0; 175 bt = binr.ReadByte(); 176 if (bt != 0x02) 177 return 0; 178 bt = binr.ReadByte(); 179
180 if (bt == 0x81) 181 count = binr.ReadByte(); 182 else
183 if (bt == 0x82) 184 { 185 highbyte = binr.ReadByte(); 186 lowbyte = binr.ReadByte(); 187 byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; 188 count = BitConverter.ToInt32(modint, 0); 189 } 190 else
191 { 192 count = bt; 193 } 194
195 while (binr.ReadByte() == 0x00) 196 { 197 count -= 1; 198 } 199 binr.BaseStream.Seek(-1, SeekOrigin.Current); 200 return count; 201 } 202
203 private RSACryptoServiceProvider CreateRsaProviderFromPublicKey(string publicKeyString) 204 { 205 // encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
206 byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 }; 207 byte[] x509key; 208 byte[] seq = new byte[15]; 209 int x509size; 210
211 x509key = Convert.FromBase64String(publicKeyString); 212 x509size = x509key.Length; 213
214 // --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
215 using (MemoryStream mem = new MemoryStream(x509key)) 216 { 217 using (BinaryReader binr = new BinaryReader(mem)) //wrap Memory Stream with BinaryReader for easy reading
218 { 219 byte bt = 0; 220 ushort twobytes = 0; 221
222 twobytes = binr.ReadUInt16(); 223 if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
224 binr.ReadByte(); //advance 1 byte
225 else if (twobytes == 0x8230) 226 binr.ReadInt16(); //advance 2 bytes
227 else
228 return null; 229
230 seq = binr.ReadBytes(15); //read the Sequence OID
231 if (!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct
232 return null; 233
234 twobytes = binr.ReadUInt16(); 235 if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
236 binr.ReadByte(); //advance 1 byte
237 else if (twobytes == 0x8203) 238 binr.ReadInt16(); //advance 2 bytes
239 else
240 return null; 241
242 bt = binr.ReadByte(); 243 if (bt != 0x00) //expect null byte next
244 return null; 245
246 twobytes = binr.ReadUInt16(); 247 if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
248 binr.ReadByte(); //advance 1 byte
249 else if (twobytes == 0x8230) 250 binr.ReadInt16(); //advance 2 bytes
251 else
252 return null; 253
254 twobytes = binr.ReadUInt16(); 255 byte lowbyte = 0x00; 256 byte highbyte = 0x00; 257
258 if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81)
259 lowbyte = binr.ReadByte(); // read next bytes which is bytes in modulus
260 else if (twobytes == 0x8202) 261 { 262 highbyte = binr.ReadByte(); //advance 2 bytes
263 lowbyte = binr.ReadByte(); 264 } 265 else
266 return null; 267 byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; //reverse byte order since asn.1 key uses big endian order
268 int modsize = BitConverter.ToInt32(modint, 0); 269
270 int firstbyte = binr.PeekChar(); 271 if (firstbyte == 0x00) 272 { //if first byte (highest order) of modulus is zero, don't include it
273 binr.ReadByte(); //skip this null byte
274 modsize -= 1; //reduce modulus buffer size by 1
275 } 276
277 byte[] modulus = binr.ReadBytes(modsize); //read the modulus bytes
278
279 if (binr.ReadByte() != 0x02) //expect an Integer for the exponent data
280 return null; 281 int expbytes = (int)binr.ReadByte(); // should only need one byte for actual exponent data (for all useful values)
282 byte[] exponent = binr.ReadBytes(expbytes); 283
284 // ------- create RSACryptoServiceProvider instance and initialize with public key -----
285 RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(); 286 RSAParameters RSAKeyInfo = new RSAParameters(); 287 RSAKeyInfo.Modulus = modulus; 288 RSAKeyInfo.Exponent = exponent; 289 RSA.ImportParameters(RSAKeyInfo); 290
291 return RSA; 292 } 293
294 } 295 } 296
297 private bool CompareBytearrays(byte[] a, byte[] b) 298 { 299 if (a.Length != b.Length) 300 return false; 301 int i = 0; 302 foreach (byte c in a) 303 { 304 if (c != b[i]) 305 return false; 306 i++; 307 } 308 return true; 309 } 310 } 311 }
虽然将公钥暴露在js文件中,但是如果需要解密得到明文,必须需要私钥(这个存储在后台,不容易获取)。
调试运行,可以看到获取的密码是加密后的数据,然后在后台可以进行解密获取到明文。