同时兼容JS和C#的RSA加密解密算法详解(对web提交的数据加密传输)
前言
我们在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>
var encrypt = new jsencrypt(); var pubkey = "-----begin public key----- \ migfma0gcsqgsib3dqebaquaa4gnadcbiqkbgqdaj0dpnbmf3z4vt1b8ee6bjkns \ hlyj7xvgijaa8rcdmgr7mrtrexnk8mdulwdcs05gc4ssfoywjcytkuhpwn8/pks0 \ vggol9bzn0xt9hiqtb3pzafyknrmdgzmgjgfd6ktnfzvuaoupvxjcgkcoj6/vv5i \ emcx8mt/z3elfsdsjqidaqab \ -----end public key-----"; encrypt.setpublickey(pubkey); var encrypted = 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#进行解密
using system; using system.collections.generic; using system.io; using system.linq; using system.security.cryptography; using system.text; using system.threading.tasks; namespace cmcloud.saas { public class rsacryptoservice { private rsacryptoserviceprovider _privatekeyrsaprovider; private rsacryptoserviceprovider _publickeyrsaprovider; /// <summary> /// rsa解密 /// </summary> /// <param name="ciphertext"></param> /// <returns></returns> public string decrypt(string ciphertext) { if (_privatekeyrsaprovider == null) { throw new exception("_privatekeyrsaprovider is null"); } return decrypt2(ciphertext); } /// <summary> /// rsa加密 /// </summary> /// <param name="text"></param> /// <returns></returns> public string encrypt(string text) { if (_publickeyrsaprovider == null) { throw new exception("_publickeyrsaprovider is null"); } return encrypt2(text); //return convert.tobase64string(_publickeyrsaprovider.encrypt(encoding.utf8.getbytes(text), false)); } private string encrypt2(string text) { byte[] plaintextdata = encoding.utf8.getbytes(text); int maxblocksize = _publickeyrsaprovider.keysize / 8 - 11;//加密块最大长度限制 if (plaintextdata.length <= maxblocksize) { return convert.tobase64string(_publickeyrsaprovider.encrypt(plaintextdata, false)); } else { using (memorystream plaistream = new memorystream(plaintextdata)) using (memorystream crypstream = new memorystream()) { byte[] buffer = new byte[maxblocksize]; int blocksize = plaistream.read(buffer, 0, maxblocksize); while (blocksize > 0) { byte[] toencrypt = new byte[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); } return convert.tobase64string(crypstream.toarray(), base64formattingoptions.none); } } } private string decrypt2(string ciphertext) { byte[] ciphertextdata = convert.frombase64string(ciphertext); int maxblocksize = _privatekeyrsaprovider.keysize / 8; //解密块最大长度限制 if (ciphertextdata.length <= maxblocksize) return system.text.encoding.utf8.getstring(_privatekeyrsaprovider.decrypt(ciphertextdata, false)); using (memorystream crypstream = new memorystream(ciphertextdata)) using (memorystream plaistream = new memorystream()) { byte[] buffer = new byte[maxblocksize]; int blocksize = crypstream.read(buffer, 0, maxblocksize); while (blocksize > 0) { byte[] todecrypt = new byte[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); } return system.text.encoding.utf8.getstring(plaistream.toarray()); } } public rsacryptoservice(string privatekey, string publickey = null) { if (!string.isnullorempty(privatekey)) { _privatekeyrsaprovider = creatersaproviderfromprivatekey(privatekey); } if (!string.isnullorempty(publickey)) { _publickeyrsaprovider = creatersaproviderfrompublickey(publickey); } } private rsacryptoserviceprovider creatersaproviderfromprivatekey(string privatekey) { var privatekeybits = system.convert.frombase64string(privatekey); var rsa = new rsacryptoserviceprovider(); var rsaparams = new rsaparameters(); using (binaryreader binr = new binaryreader(new memorystream(privatekeybits))) { byte bt = 0; ushort twobytes = 0; twobytes = binr.readuint16(); if (twobytes == 0x8130) binr.readbyte(); else if (twobytes == 0x8230) binr.readint16(); else throw new exception("unexpected value read binr.readuint16()"); twobytes = binr.readuint16(); if (twobytes != 0x0102) throw new exception("unexpected version"); bt = binr.readbyte(); if (bt != 0x00) throw new exception("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); return rsa; } private int getintegersize(binaryreader binr) { byte bt = 0; byte lowbyte = 0x00; byte highbyte = 0x00; int count = 0; bt = binr.readbyte(); if (bt != 0x02) return 0; bt = binr.readbyte(); if (bt == 0x81) count = binr.readbyte(); else if (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); return count; } private rsacryptoserviceprovider creatersaproviderfrompublickey(string publickeystring) { // 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 = new byte[15]; int x509size; x509key = convert.frombase64string(publickeystring); x509size = x509key.length; // --------- set up stream to read the asn.1 encoded subjectpublickeyinfo blob ------ using (memorystream mem = new memorystream(x509key)) { using (binaryreader binr = new binaryreader(mem)) //wrap memory stream with binaryreader for easy reading { byte bt = 0; ushort twobytes = 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 byte else if (twobytes == 0x8230) binr.readint16(); //advance 2 bytes else return null; seq = binr.readbytes(15); //read the sequence oid if (!comparebytearrays(seq, seqoid)) //make sure sequence for oid is correct return null; 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 byte else if (twobytes == 0x8203) binr.readint16(); //advance 2 bytes else return null; bt = binr.readbyte(); if (bt != 0x00) //expect null byte next return null; twobytes = binr.readuint16(); if (twobytes == 0x8130) //data read as little endian order (actual data order for sequence is 30 81) binr.readbyte(); //advance 1 byte else if (twobytes == 0x8230) binr.readint16(); //advance 2 bytes else return null; twobytes = binr.readuint16(); byte lowbyte = 0x00; byte highbyte = 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 modulus else if (twobytes == 0x8202) { highbyte = binr.readbyte(); //advance 2 bytes lowbyte = binr.readbyte(); } else return null; byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; //reverse byte order since asn.1 key uses big endian order int modsize = bitconverter.toint32(modint, 0); int firstbyte = binr.peekchar(); if (firstbyte == 0x00) { //if first byte (highest order) of modulus is zero, don't include it binr.readbyte(); //skip this null byte modsize -= 1; //reduce modulus buffer size by 1 } byte[] modulus = binr.readbytes(modsize); //read the modulus bytes if (binr.readbyte() != 0x02) //expect an integer for the exponent data return null; int expbytes = (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 ----- rsacryptoserviceprovider rsa = new rsacryptoserviceprovider(); rsaparameters rsakeyinfo = new rsaparameters(); rsakeyinfo.modulus = modulus; rsakeyinfo.exponent = exponent; rsa.importparameters(rsakeyinfo); return rsa; } } } private bool comparebytearrays(byte[] a, byte[] b) { if (a.length != b.length) return false; int i = 0; foreach (byte c in a) { if (c != b[i]) return false; i++; } return true; } } }
虽然将公钥暴露在js文件中,但是如果需要解密得到明文,必须需要私钥(这个存储在后台,不容易获取)。
调试运行,可以看到获取的密码是加密后的数据,然后在后台可以进行解密获取到明文。
总结
好了,大概就这样,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持