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
以上命令会创建如下的文件:
这个文件可以用文本编辑器进行打开,查看内容。
-----BEGINPUBLICKEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0w036ClSD0LvxPROMun0u022ROJlZE6P3m+gjq3gpi4n7lo8jhTqMqgccDbVJqnIfMzWS9O3lnlQXWTxJ3B4XJ52FAcriY5brOXUVgBLx5QMHLLd1gtJnmG4i7r4ytgX7XVKRnojR6zca1YnS0lbGGDF1CGllB1riNrdksSQP+wIDAQAB-----ENDPUBLICKEY-----
-----BEGINRSAPRIVATEKEY-----MIICXQIBAAKBgQC0w036ClSD0LvxPROMun0u022ROJlZE6P3m+gjq3gpi4n7lo8jhTqMqgccDbVJqnIfMzWS9O3lnlQXWTxJ3B4XJ52FAcriY5brOXUVgBLx5QMHLLd1gtJnmG4i7r4ytgX7XVKRnojR6zca1YnS0lbGGDF1CGllB1riNrdksSQP+wIDAQABAoGAIOyl6lIxXKULZoBKbEqXfIz0GwxlGg1ywyn5mW2lAGQzKMken0ioBnD9xIVWrOlHyhkIvBCyuC0jgfE2Avn93MlB3j0WRuXMFlJpCBlEklMilO9Zgmwl+vTB3VZb8VzdrEEEUBio7LWP/KvSo+IFlNjDTKgAczbLTwAmj4w6g0ECQQDm4yxPdxcU2ywZ7PyjIMM9qnSah9KcrjU8gjEyHsUpgTjhw1cx7Peo+vRiHqxDy1yaSu1BlwRR52pCjKNnl0QhAkEAyGx3NxEIiLk2oXGGbIMZ4P6geC8gYu01BiRNWVf0Yi7+sCH68eUPoI+G5bJ8bvzXpvHjQi0s2OlRfct/qtPQmwJBALa+2DONbxdy4lUi3lO/esk0QVaOaoTY3gomggnJkQRo4zzOABXkGaIF/6gp3u9J5uG4rFFd1m19XP2Pk0ZK1AECQBYilJAKW4zuF7CA3z3AxOzqckKTwdnrJL4G6FwDsMPfONWvCw4IJE+xSk64BbIkTpTrhhPa9WcHba6c+P6e4h0CQQDWeGMMpkqPG/w4afNCGmvRnM8vNkGUAmDGvCsfkTIDijpKl5SD55hPHsWE5rsv1TLUpkWtrFBcg61bHwMUP3cv-----ENDRSAPRIVATEKEY-----
2 用jsencrypt对密码进行加密:
首先需要导入js包文件:
<scriptsrc="dist/js/jsencrypt.js"></script>
然后编写JS加密算法,示例如下:
varencrypt=newJSEncrypt(); varpubkey="-----BEGIN PUBLIC KEY----- \MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAj0dPnBMf3Z4VT1B8Ee6bjKNs \hlYj7xvGijAa8RCdmGR7mrtrExnk8mdUlwdcS05gc4SSFOyWJcYtKUHpWn8/pkS0 \vgGOl9Bzn0Xt9hiqTb3pZAfykNrMDGZMgJgfD6KTnfzVUAOupvxjcGkcoj6/vV5I \eMcx8mT/z3elfsDSjQIDAQAB \-----END PUBLIC KEY-----"; encrypt.setPublicKey(pubkey); varencrypted=encrypt.encrypt($('#txtpwd').val()); //console.log(encrypted);$.ajax({ type: "POST", url: "http://localhost:24830/services/rsa_pem.ashx", data: { "pwd": encrypted }, dataType: "Json", error: function (xhr, status, error) { // alert(error);$("#txtInfo").text(' 请求服务器失败!'); $(that).text('登 录'); $(that).attr('disabled', false); }, success: function (json) { if (uid=="admin"&&json.data=="000") { window.location.href="index.html"; } else { $("#txtInfo").text(' 用户名或者密码错误!'); $(that).text('登 录'); $(that).attr('disabled', false); } } });
3 后台用C#进行解密
usingSystem; usingSystem.Collections.Generic; usingSystem.IO; usingSystem.Linq; usingSystem.Security.Cryptography; usingSystem.Text; usingSystem.Threading.Tasks; namespaceCMCloud.SaaS{ publicclassRSACryptoService { privateRSACryptoServiceProvider_privateKeyRsaProvider; privateRSACryptoServiceProvider_publicKeyRsaProvider; /// <summary>/// RSA解密/// </summary>/// <param name="cipherText"></param>/// <returns></returns>publicstringDecrypt(stringcipherText) { if (_privateKeyRsaProvider==null) { thrownewException("_privateKeyRsaProvider is null"); } returnDecrypt2(cipherText); } /// <summary>/// RSA加密/// </summary>/// <param name="text"></param>/// <returns></returns>publicstringEncrypt(stringtext) { if (_publicKeyRsaProvider==null) { thrownewException("_publicKeyRsaProvider is null"); } returnEncrypt2(text); //return Convert.ToBase64String(_publicKeyRsaProvider.Encrypt(Encoding.UTF8.GetBytes(text), false)); } privatestringEncrypt2(stringtext) { Byte[] PlaintextData=Encoding.UTF8.GetBytes(text); intMaxBlockSize=_publicKeyRsaProvider.KeySize/8-11;//加密块最大长度限制if (PlaintextData.Length<=MaxBlockSize) { returnConvert.ToBase64String(_publicKeyRsaProvider.Encrypt(PlaintextData, false)); } else { using (MemoryStreamPlaiStream=newMemoryStream(PlaintextData)) using (MemoryStreamCrypStream=newMemoryStream()) { Byte[] Buffer=newByte[MaxBlockSize]; intBlockSize=PlaiStream.Read(Buffer, 0, MaxBlockSize); while (BlockSize>0) { Byte[] ToEncrypt=newByte[BlockSize]; Array.Copy(Buffer, 0, ToEncrypt, 0, BlockSize); Byte[] Cryptograph=_publicKeyRsaProvider.Encrypt(ToEncrypt, false); CrypStream.Write(Cryptograph, 0, Cryptograph.Length); BlockSize=PlaiStream.Read(Buffer, 0, MaxBlockSize); } returnConvert.ToBase64String(CrypStream.ToArray(), Base64FormattingOptions.None); } } } privatestringDecrypt2(stringciphertext) { Byte[] CiphertextData=Convert.FromBase64String(ciphertext); intMaxBlockSize=_privateKeyRsaProvider.KeySize/8; //解密块最大长度限制if (CiphertextData.Length<=MaxBlockSize) returnSystem.Text.Encoding.UTF8.GetString(_privateKeyRsaProvider.Decrypt(CiphertextData, false)); using (MemoryStreamCrypStream=newMemoryStream(CiphertextData)) using (MemoryStreamPlaiStream=newMemoryStream()) { Byte[] Buffer=newByte[MaxBlockSize]; intBlockSize=CrypStream.Read(Buffer, 0, MaxBlockSize); while (BlockSize>0) { Byte[] ToDecrypt=newByte[BlockSize]; Array.Copy(Buffer, 0, ToDecrypt, 0, BlockSize); Byte[] Plaintext=_privateKeyRsaProvider.Decrypt(ToDecrypt, false); PlaiStream.Write(Plaintext, 0, Plaintext.Length); BlockSize=CrypStream.Read(Buffer, 0, MaxBlockSize); } returnSystem.Text.Encoding.UTF8.GetString(PlaiStream.ToArray()); } } publicRSACryptoService(stringprivateKey, stringpublicKey=null) { if (!string.IsNullOrEmpty(privateKey)) { _privateKeyRsaProvider=CreateRsaProviderFromPrivateKey(privateKey); } if (!string.IsNullOrEmpty(publicKey)) { _publicKeyRsaProvider=CreateRsaProviderFromPublicKey(publicKey); } } privateRSACryptoServiceProviderCreateRsaProviderFromPrivateKey(stringprivateKey) { varprivateKeyBits=System.Convert.FromBase64String(privateKey); varRSA=newRSACryptoServiceProvider(); varRSAparams=newRSAParameters(); using (BinaryReaderbinr=newBinaryReader(newMemoryStream(privateKeyBits))) { bytebt=0; ushorttwobytes=0; twobytes=binr.ReadUInt16(); if (twobytes==0x8130) binr.ReadByte(); elseif (twobytes==0x8230) binr.ReadInt16(); elsethrownewException("Unexpected value read binr.ReadUInt16()"); twobytes=binr.ReadUInt16(); if (twobytes!=0x0102) thrownewException("Unexpected version"); bt=binr.ReadByte(); if (bt!=0x00) thrownewException("Unexpected value read binr.ReadByte()"); RSAparams.Modulus=binr.ReadBytes(GetIntegerSize(binr)); RSAparams.Exponent=binr.ReadBytes(GetIntegerSize(binr)); RSAparams.D=binr.ReadBytes(GetIntegerSize(binr)); RSAparams.P=binr.ReadBytes(GetIntegerSize(binr)); RSAparams.Q=binr.ReadBytes(GetIntegerSize(binr)); RSAparams.DP=binr.ReadBytes(GetIntegerSize(binr)); RSAparams.DQ=binr.ReadBytes(GetIntegerSize(binr)); RSAparams.InverseQ=binr.ReadBytes(GetIntegerSize(binr)); } RSA.ImportParameters(RSAparams); returnRSA; } privateintGetIntegerSize(BinaryReaderbinr) { bytebt=0; bytelowbyte=0x00; bytehighbyte=0x00; intcount=0; bt=binr.ReadByte(); if (bt!=0x02) return0; bt=binr.ReadByte(); if (bt==0x81) count=binr.ReadByte(); elseif (bt==0x82) { highbyte=binr.ReadByte(); lowbyte=binr.ReadByte(); byte[] modint= { lowbyte, highbyte, 0x00, 0x00 }; count=BitConverter.ToInt32(modint, 0); } else { count=bt; } while (binr.ReadByte() ==0x00) { count-=1; } binr.BaseStream.Seek(-1, SeekOrigin.Current); returncount; } privateRSACryptoServiceProviderCreateRsaProviderFromPublicKey(stringpublicKeyString) { // encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"byte[] SeqOID= { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 }; byte[] x509key; byte[] seq=newbyte[15]; intx509size; x509key=Convert.FromBase64String(publicKeyString); x509size=x509key.Length; // --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------using (MemoryStreammem=newMemoryStream(x509key)) { using (BinaryReaderbinr=newBinaryReader(mem)) //wrap Memory Stream with BinaryReader for easy reading { bytebt=0; ushorttwobytes=0; twobytes=binr.ReadUInt16(); if (twobytes==0x8130) //data read as little endian order (actual data order for Sequence is 30 81)binr.ReadByte(); //advance 1 byteelseif (twobytes==0x8230) binr.ReadInt16(); //advance 2 byteselsereturnnull; seq=binr.ReadBytes(15); //read the Sequence OIDif (!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correctreturnnull; twobytes=binr.ReadUInt16(); if (twobytes==0x8103) //data read as little endian order (actual data order for Bit String is 03 81)binr.ReadByte(); //advance 1 byteelseif (twobytes==0x8203) binr.ReadInt16(); //advance 2 byteselsereturnnull; bt=binr.ReadByte(); if (bt!=0x00) //expect null byte nextreturnnull; twobytes=binr.ReadUInt16(); if (twobytes==0x8130) //data read as little endian order (actual data order for Sequence is 30 81)binr.ReadByte(); //advance 1 byteelseif (twobytes==0x8230) binr.ReadInt16(); //advance 2 byteselsereturnnull; twobytes=binr.ReadUInt16(); bytelowbyte=0x00; bytehighbyte=0x00; if (twobytes==0x8102) //data read as little endian order (actual data order for Integer is 02 81)lowbyte=binr.ReadByte(); // read next bytes which is bytes in moduluselseif (twobytes==0x8202) { highbyte=binr.ReadByte(); //advance 2 byteslowbyte=binr.ReadByte(); } elsereturnnull; byte[] modint= { lowbyte, highbyte, 0x00, 0x00 }; //reverse byte order since asn.1 key uses big endian orderintmodsize=BitConverter.ToInt32(modint, 0); intfirstbyte=binr.PeekChar(); if (firstbyte==0x00) { //if first byte (highest order) of modulus is zero, don't include itbinr.ReadByte(); //skip this null bytemodsize-=1; //reduce modulus buffer size by 1 } byte[] modulus=binr.ReadBytes(modsize); //read the modulus bytesif (binr.ReadByte() !=0x02) //expect an Integer for the exponent datareturnnull; intexpbytes= (int)binr.ReadByte(); // should only need one byte for actual exponent data (for all useful values)byte[] exponent=binr.ReadBytes(expbytes); // ------- create RSACryptoServiceProvider instance and initialize with public key -----RSACryptoServiceProviderRSA=newRSACryptoServiceProvider(); RSAParametersRSAKeyInfo=newRSAParameters(); RSAKeyInfo.Modulus=modulus; RSAKeyInfo.Exponent=exponent; RSA.ImportParameters(RSAKeyInfo); returnRSA; } } } privateboolCompareBytearrays(byte[] a, byte[] b) { if (a.Length!=b.Length) returnfalse; inti=0; foreach (bytecina) { if (c!=b[i]) returnfalse; i++; } returntrue; } } }
虽然将公钥暴露在js文件中,但是如果需要解密得到明文,必须需要私钥(这个存储在后台,不容易获取)。调试运行,可以看到获取的密码是加密后的数据,然后在后台可以进行解密获取到明文。