[关闭]
@octopus 2019-10-23T08:20:54.000000Z 字数 5954 阅读 3310

js+php RSA & AES 加密解密

php


有一个实名认证的需求,要求用户信息提交时不可明文传输,尝试了一天的RSA非对称加解密,苦逼的撞上了所有的坑,做个详细记录。

前情提要:什么是 RSA 非对称加密

1. 准备一对公私秘钥

2. 前端 js 加密数据

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title></title>
  6. </head>
  7. <body>
  8. <!-- 引入加密必要的文件 -->
  9. <script src="http://cdn.bootcss.com/jquery/3.0.0/jquery.min.js"></script>
  10. <script src="https://passport.cnblogs.com/scripts/jsencrypt.min.js"></script>
  11. <!-- 注意,这个文件需要下载后修改 【坑2】-->
  12. <script src="http://www-cs-students.stanford.edu/~tjw/jsbn/base64.js"></script>
  13. <!-- 自定义的文件 -->
  14. <script src="function.js"></script>
  15. <script>
  16. // 1. 构造参数(json格式)
  17. var json = {
  18. "realname":"小王",
  19. "age":18
  20. }
  21. // 2. 准备好公钥(因为是公钥,暴露在js中也完全ok,解密用的私钥在php端才有)
  22. var pubkey='-----BEGIN PUBLIC KEY-----';
  23. pubkey+='MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvcaNOqy03ZEJyWbbjBZTnSaFkp2F85/Ha1ufAa15Fxp4Oi6pxzBh3YgruxHL8skMM1LjFlfcuyrvOw60kEjCFlfjSYxJZ1rhFnYK3mZFtpvreQrO2Xy2y2aIE6Zs6CMpFrpNvR7PUQ6de3I2j3CDqUV130pgPq/HdPY7jpx4tkQIDAQAB';
  24. pubkey+='-----END PUBLIC KEY-----';
  25. // 2. 调用加密函数,获得加密后的参数字符串
  26. // 此函数封装在 function.js 中 【坑3】
  27. var res = encrypt(pubkey, json);
  28. document.write(res);
  29. // 3. 将参数传给后端
  30. var toPHP = {"data":res};
  31. ...
  32. </script>
  33. </body>
  34. </html>
  1. // function.js
  2. // 此函数扩展了 encrypt 类的功能,增加了 encryptLong 成员方法,可以解密长字符串。
  3. // 直接拷贝即可
  4. function encrypt(pubkey, str){
  5. JSEncrypt.prototype.encryptLong = function(string) {
  6. var k = this.getKey();
  7. try {
  8. var lt = "";
  9. var ct = "";
  10. //RSA每次加密117bytes,需要辅助方法判断字符串截取位置
  11. //1.获取字符串截取点
  12. var bytes = new Array();
  13. bytes.push(0);
  14. var byteNo = 0;
  15. var len,c;
  16. len = string.length;
  17. var temp = 0;
  18. for(var i = 0; i < len; i++){
  19. c = string.charCodeAt(i);
  20. if(c >= 0x010000 && c <= 0x10FFFF){
  21. byteNo += 4;
  22. }else if(c >= 0x000800 && c <= 0x00FFFF){
  23. byteNo += 3;
  24. }else if(c >= 0x000080 && c <= 0x0007FF){
  25. byteNo += 2;
  26. }else{
  27. byteNo += 1;
  28. }
  29. if((byteNo % 117) >= 114 || (byteNo % 117) == 0){
  30. if(byteNo-temp >= 114){
  31. bytes.push(i);
  32. temp = byteNo;
  33. }
  34. }
  35. }
  36. //2.截取字符串并分段加密
  37. if(bytes.length > 1){
  38. for(var i=0;i< bytes.length-1; i++){
  39. var str;
  40. if(i == 0){
  41. str = string.substring(0,bytes[i+1]+1);
  42. }else{
  43. str = string.substring(bytes[i]+1,bytes[i+1]+1);
  44. }
  45. var t1 = k.encrypt(str);
  46. ct += t1;
  47. };
  48. if(bytes[bytes.length-1] != string.length-1){
  49. var lastStr = string.substring(bytes[bytes.length-1]+1);
  50. ct += k.encrypt(lastStr);
  51. }
  52. return hex2b64(ct);
  53. }
  54. var t = k.encrypt(string);
  55. var y = hex2b64(t);
  56. return y;
  57. } catch (ex) {
  58. return ex;
  59. }
  60. };
  61. // 利用公钥加密
  62. var encrypt = new JSEncrypt();
  63. encrypt.setPublicKey(pubkey);
  64. var encrypted = encrypt.encryptLong(JSON.stringify(str));
  65. return encrypted;
  66. }

3. php端解密

  1. $data = $_POST["data"]; // 获得加密过的数据
  2. $arr = $this->RSAEncode($data); // 解密
  3. $arr = json_decode($arr, true);
  4. print_r($arr);
  5. // 分段解密算法
  6. private function RSAEncode($str){
  7. $private_key = '-----BEGIN RSA PRIVATE KEY-----
  8. MIICXAIBAAKBgQCvcaNOqy03ZEJyWbbjBZTnSaFkp2F85/Ha1ufAa15Fxp4Oi6pxzBh3YgruxHL8skMM1LjFlfcuyrvOw60kEjCFlfjSYxJZ1rhFnYK3mZFtpvreQrO2Xy2y2aIE6Zs6CMpFrpNvR7PUQ6de3I2j3CDqUV130pgPq/HdPY7jpx4tkQIDAQABAoGAXRtVq340dN0EVmM+J6TirQvqVtxtZDhDpfu+6eMRGL6bSuTcA6Boq5Kgcq5Wx7xi0QBjPpijoXV9zfEShb4ReTIUyuaaiCC7zIhOQmH8nZVOiz6YLxvJwVOF3hNWi9rvusz0bBCWpe8XA/AnUR64QM+yzxXavy77yWWrnkbQj/ECQQDgzef24QUznQviQNa+U2nIyV/7eTNAAT6J3A/ObQIzJfdGTKLAC4q88q8viHEd2kItgaiwlD7rhrNQHxCij37XAkEAx8o4Y9maJ9UVuQ7dWdnipJp66deS5ysP9pG+oRRBiiAoKCg6/VjsAURIdST8hnGHMdEo9Mc3OitysqmmMFix1wJAFP8GQEep/bUYTAx0Qhh2U9uDDHC4eazkE2orS8NgpjhQjQ9RUBCD57ve4tiigABHmAp5H+zop4TQMJaGLjiqxwJBALY2VUWjwTeQ3JYB25lF4bHT/kPlS2Kv4Ig1llGcgsWx3E37mwlSnzPsjdThHbqUMgQaSastQc44sAwIM7ymN9MCQGECUqDAs5oHO98V937Br9Ou14tuRN2gw52dqBaBnme8Eszqg2Z2PbioWKogGJwCDHV2JnLHqdBnJ76R7eyrYck=
  9. -----END RSA PRIVATE KEY-----';
  10. $crypto = '';
  11. $str = str_replace(array(" ",'-','_'),array("+",'+','/'), $str); // 关键
  12. foreach (str_split(base64_decode($str), 128) as $chunk)
  13. {
  14. $res = openssl_private_decrypt($chunk, $decryptData, $private_key);
  15. if(!$res){
  16. return false;
  17. }
  18. $crypto .= $decryptData;
  19. }
  20. return str_replace('"','',$crypto);
  21. }

4. 填坑

坑1:关于密钥长度大小选择

长度越大,解密时间越长,但是长度小的,如 1024,只能加密解密很短的字符串,稍微多几个表单字段就gg,但是选了 2048也治标不治本,无非是比1024多了点长度。解决办法是 `分段加密`,`分段解密`,for循环几个字符一加密,最后拼起来就行啦,解密也是同理,所以密钥长度选最小的 1024 即可,无论字符串多大,我们都能分段加解密。

坑2:base64.js

网上下载的 base64.js 中第一行定义编码字符是这样的:

  1. var b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

但是,亲测php接收到用它编码加密后的参数,再解密大概率失败。原因在于 + 和 / 这两个特殊符号可能解析会出现问题。解决办法:

  1. // 前端修改 base64.js,将上面那个用来编码的字符串改为:
  2. var b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
  3. // 后端转义时先替换:
  4. $str = str_replace(array(" ",'-','_'),array("+",'+','/'), $str);

坑3

为什么要增加 encryptLong 这个成员方法呢,这也是对 [坑1] 的补充,即如何分段加密。

js+php AES 加密解密

前情提要:什么是 AES 对称加密

1. 前端 js 加密数据

  1. <script src="core.js"></script>
  2. <script src="aes.js"></script>
  3. <script>
  4. var send = "hi, world";
  5. var key = "123456781234567812345678"; // 24
  6. var vi = "1234567812345678"; // 16位加密偏移量,与后端一致
  7. var key = CryptoJS.enc.Latin1.parse(key);
  8. var iv = CryptoJS.enc.Latin1.parse(iv);
  9. var aes_send = CryptoJS.AES.encrypt(send, key, {
  10. iv: iv,
  11. mode: CryptoJS.mode.CBC,
  12. adding: CryptoJS.pad.ZeroPadding
  13. }).toString();
  14. console.log(aes_send); // 加密后的数据
  15. </script>

2. 后端 php 解密数据

  1. <?php
  2. openssl_decrypt("加密后的数据", 'AES-192-CBC', '123456781234567812345678', 0, '1234567812345678');
  3. ?>
  4. // 'AES-192-CBC'是解密方式,根据秘钥位数确定,24位秘钥用此方式

SRA + AES 前后端传参的完美加密方案

方案

- SRA 公私钥虽安全,但加密长度过大的字符串耗时很长
- AES 虽不安全,但擅长加密长度过大的字符串
  1. 前端用随机函数生成一串秘钥,保存在变量中 (解决了明文秘钥的问题)
  2. 对秘钥 SRA 加密
  3. 用秘钥对长参数进行 AES 加密
  4. 将 SRA 加密后的秘钥,与 AES 加密后的参数,传给后端。
  5. 后端用私钥对秘钥解密,得到真正的秘钥
  6. 用秘钥对 AES 加密后的参数解密,得到参数

代码

前端代码已上传百度云:https://pan.baidu.com/s/1mJh28RFaHgcqUKY2mB6_yQ
后端如下:

  1. <?php
  2. namespace lib\encryption;
  3. class Encryption
  4. {
  5. private $private_key = '-----BEGIN RSA PRIVATE KEY-----
  6. xxxxxxx自己去申请吧xxxxxxxx
  7. -----END RSA PRIVATE KEY-----';
  8. private $aes_vi = '1234567812345678';
  9. public function RSADecode($str){
  10. $crypto = '';
  11. $str = str_replace(array(" ",'-','_'),array("+",'+','/'), $str);
  12. foreach (str_split(base64_decode($str), 128) as $chunk)
  13. {
  14. $res = openssl_private_decrypt($chunk, $decryptData, $this->private_key);
  15. if(!$res){
  16. return false;
  17. }
  18. $crypto .= $decryptData;
  19. }
  20. return str_replace('"','',$crypto);
  21. }
  22. public function AESDecode($key, $data){
  23. return openssl_decrypt($data, 'AES-192-CBC', $key, 0, $this->aes_vi);
  24. }
  25. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注