小程序之RSA加密实现接口保护和多并发处理

前言

近期由于客户现场出现了非法接口的调用,为了处理此类问题和杜绝后续问题的出现。针对公司现有的项目实现接口加密,防止接口被恶意的调用。

设计思路:客户端存储超级私钥,客户端返回加密的公钥。客户端先使用超级私钥对公钥进行解密,使用解密出来的公钥对接口进行加密。把加密后的字符串进行验签发送给服务器,服务器进行字符串对比。

相关引用文件

文件的引用这里就只放截图了,源文件网上很容易找到的。这里使用jsencrypt.min.js是为了节省小程序的空间。
在这里插入图片描述在这里插入图片描述

获取服务器的加密公钥

var baseURL = require('./api.js');
var superPrivateKey = "MIIEpQIBAAKCAQEAyNftZEtP9PdwCZT8ZE8xSzuLA8tSZbczwBaLg+NiWKibCGsk11YXYcL8kZ9DBxVdpv1ysp7vGBu6QdmDKeymeae6HCEu3ThDjzGMbCCITswIWjT01Y7Fa6MQlbYJZD55MY3w/ZWDnzsjuTF6l3KJodqUwhXUlUvTbi60gdQXOmgNUE3LkGc9dsDonKBId46WLMY92B3vlLoA/GArBeCZQcwF29ztfrfvRP2tCtvV9tsqoRXSJXzbEgI3fX2/WVt9drXgMd9ZekM53PZwwEHQyEwnLrilOTAzlmzGO2ZbXY+4ahUo2iOH5WAvA65Y/qw7QgIzT24ooJ1HY1eyyFJUnwIDAQABAoIBAQC0lWF/Yi/8RFbaZrkgwAvEZz8xJClsB3NibWL4LQUKTl9HDH9NlrzjS9yoWph7z/wq34u3iyvTc2rfNmG22m88x1CRZkyq36HvKE/oEMA+iTmhUigptHtRsoaA9fIvzfROWB+tAjgcfaI7K3/cmEGj49MJR3Oi4VCzqw7mBPr1qWvNFx5vd4Rwjy4qO5K+BAlx8+yQ87z04tULlzUqBPIPK8WGb98TlG1CmMw5/ZQo1HfjxA82+8AO3qMapPDC6hzUBPvS+AQ2NxRdjceDxPmNzA2s2Kp2DIN3sQalSSy3+9ESLQpwwLhSJSmX/6E1XZFhv76nd3zOtbSUrzLBwzuxAoGBAPaVYfGMod/9eAZGQsCSv3sGQSis//jhjtHxef3VMdhGr3vgCmokLb1RlP50zVYlXYh3qvksn7ZjC5XkVbVDofNC4MJatzi4rIpQ/uUNmlxMMhZkwn8S4C9gIgkxXsY4siSb4zNKT6SxOFazi4qeMbwE+PlaYe2mxwl+iizw4slDAoGBANCDYwNEVFUE6F+ver9Lt25mBVP8L+pOjXSCGb3c9M2YvKVKuEZcOAwE/HnZms5xLAZGG7SCVbkLCUlOHoKigbKYr1jEvujMhbSF0IuhXHSnuvWmUX1HSo/bzdEyLEaQyZ+G2bQpYYUgKjEtfPcbHuDelUw99JY3ghoV04vv3DN1AoGBAK66JaAK/f2BV1Zi3RQmKEbdpLhU9kD+W7yKdt4V/u75D4ogtGCH6F1ZfNFeJM5hRcjYuy87nqSXxHLfTJhYJ17/ydIOg/xOZ/zO7f+SxwmV+HwDxApVbsRDQ3ruH/En5ZupVrJWet8BsSFGsp9z/1vyzhWrJO9ImYsxvmmf+6OPAoGALlMCTFeB5OGSPq/dtWI8/mnsBRyiCIwrIRdGYMgWGxcz0gUnq3oReZoh/XA61GKQRVSOEyxhnxq0lXSlkqBH8EW7rx0GzPGjQtf33Q2cXM5m2ux4bjzIc+2BbFiZPZQtNyPeegg3gjwDI6nXeY6s7YiF4spg7H6oiOMQfKZtZYECgYEAttlJveIjoMWNrnPWeegZ0VU+6HT7A4okcIIE0CLEmE9qVLoGGnehgpntf71spCePJOJhaPydZsDZPSrz916gHtg1vGwoApAgecweom3s95hKyOMsN6LoZRvrR7mx9UQ4Qjaq8bZzzxhF+XsvzC9QYBVUzeZp6y2rkXgKktBQ37c="
var Encrypt = require('../utils/jsencrypt.min.js');
var encryptor = new Encrypt.JSEncrypt();

function timest() {
  let tmp = Date.parse(new Date()).toString();
  tmp = tmp.substr(0, 10);
  return tmp;
}

function getPrivatekey() {
  return new Promise(function(resolve, reject) {
    let time = timest();
    wx.request({
      url: baseURL.baseURL + 'api/services/app/Setting/GetCerPriKeyAsync?timestamp=' + time,
      data: '',
      header: {},
      method: 'Get',
      success: function(res) {
        encryptor.setPrivateKey(superPrivateKey);
        let array = res.data.Result.Data;
        let sourcePrivate = '';
        for (let a = 0; a < array.length; a++) {
          // sourcePrivate = encryptor.decrypt(array[0]) + array[1];
          if (a == 0) {
            sourcePrivate += encryptor.decrypt(array[a]);
          } else {
            sourcePrivate += array[a];
          }
        }
        console.log("原始私钥" + sourcePrivate);
        resolve(sourcePrivate);
      },
      fail: function(res) {
        console.log(res)
        reject(res)
      },
      complete: function(res) {},
    })
  });
}
module.exports = {
  getPrivatekey: getPrivatekey
}
baseURL是定义接口地址,在获取加密公钥的时候由于RSA自身的限制解密的字节大小最多是128个。所以在此处采用分段解密,同时为了优化小程序首次的等待时间。只返回部分公钥,并且只有第一段公钥是加密的。这在很大程度上减少了等待的时间,让用户的体验更为流畅。
在这里将获取加密公钥的方法封账成一个Promise方法,是为了保证小程序进行编译的时候在app.js尚未执行完成的情况先,index.js中的方法进行先行调用时没有公钥的情况。

处理首次获取加密公钥失败的情况

//app.js
// 引入请求文件
const http = require('/server/request.js');
const sm2 = require('/server/sm2.js');
const api = require('/server/api.js');
const util = require('/utils/weapp.js');
App({
  globalData: {
    uuid: ''
  },
  onLaunch: function(e) {
    this.init();
    this.globalData.uuid = wx.getStorageSync('uuid');
    wx.clearStorageSync();
    wx.setStorageSync('uuid', this.globalData.uuid);
  },
  // 初始化
  async init() {
    await this.getPrivatekey(); // 请求数据
    this.getHomeSetting(); // 等待请求数据成功后
    this.checkUpdateVersion(); //更新版本号
  },
  // 获取系统设置
  getHomeSetting() {
    let that = this;
    http.requestLoading('api/services/app/CRMMemberService/GetHomeSetting', {}, 'GET').then(res => {
      if (res.data.Result.Code === 0) {
        let skin = "";
        if (res.data.Result.Data.SkinColor == 0) {
          skin = "orange";
          that.globalData.skin = skin;
          that.setorangeTabBar();
        } else if (res.data.Result.Data.SkinColor == 1) {
          skin = "blue";
          that.globalData.skin = skin;
          that.setBlueTabBar()
        } else if (res.data.Result.Data.SkinColor == 2) {
          skin = "pink";
          that.globalData.skin = skin;
        }
        //保存到本地
        wx.setStorageSync('skin', skin);
        wx.setStorageSync('isShow', res.data.Result.Data.ShopGoodClassShowType);
        wx.setStorageSync('ScoreShopBannerImg', res.data.Result.Data.ScoreShopBannerImg);
      }
    })
  },
  // 小程序版本检测
  checkUpdateVersion() {
    //判断微信版本是否 兼容小程序更新机制API的使用
    if (wx.canIUse('getUpdateManager')) {
      //创建 UpdateManager 实例
      const updateManager = wx.getUpdateManager();
      console.log('是否进入模拟更新');
      //检测版本更新
      updateManager.onCheckForUpdate(function(res) {
        console.log('是否获取版本');
        // 请求完新版本信息的回调
        if (res.hasUpdate) {
          //监听小程序有版本更新事件
          updateManager.onUpdateReady(function() {
            console.log('获取版本');
            wx.showModal({
              title: '更新提示',
              content: '新版本已经准备好,是否马上重启小程序?',
              success: function(res) {
                if (res.confirm) {
                  //TODO 新的版本已经下载好,调用 applyUpdate 应用新版本并重启 ( 此处进行了自动更新操作)
                  updateManager.applyUpdate();
                }
              }
            })
          })
          updateManager.onUpdateFailed(function() {
            // 新版本下载失败
            wx.showModal({
              title: '已经有新版本喽~',
              content: '请您删除当前小程序,到微信 “发现-小程序” 页,重新搜索打开哦~',
            })
          })
        }
      })
    } else {
      //TODO 此时微信版本太低(一般而言版本都是支持的)
      wx.showModal({
        title: '溫馨提示',
        content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
      })
    }
  },
  //获取私钥
  getPrivatekey() {
    return new Promise((resolve, reject) => {
      if (this.globalData.privateKey) {
        resolve(this.globalData.privateKey);
      } else {
        sm2.getPrivatekey().then((res) => {
            this.globalData.privateKey = res;
            resolve(res);
          })
          .catch((err) => {
            console.error(err);
            reject(err);
          })
      }
    })
  }
})
app.js中针对获取公钥的方法外层又封装了一层Promise方法,是为了使用async await的方法。使获取设置信息的接口等待获取到公钥后在进行请求。在调用getPrivatekey方法的时候,会先判断当前是否已经有公钥了。存在的话则会直接使用不在请求获取公钥的接口。

加密参数进行验签及多并发处理

var baseURL = require('./api.js');
var appJs;
// 展示进度条的网络请求
// url:网络请求的url
// params:请求参数
// message:进度条的提示信息
// methods:请求方式
// hideLoad:隐藏wx.showLoading
// hideToast:隐藏wx.showToast
//用于生成uuid
function S4() {
  return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}

function guid() {
  return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
}

function objKeySort(obj) { //排序的函数
  let newkey = Object.keys(obj).sort();
  //先用Object内置类的keys方法获取要排序对象的属性名,再利用Array原型上的sort方法对获取的属性名进行排序,newkey是一个数组
  let newObj = {}; //创建一个新的对象,用于存放排好序的键值对
  for (let i = 0; i < newkey.length; i++) { //遍历newkey数组
    newObj[newkey[i]] = obj[newkey[i]]; //向新创建的对象中按照排好的顺序依次增加键值对
  }
  return newObj; //返回排好序的新对象
}

function timest() {
  let tmp = Math.round(new Date() / 1000);
  return tmp;
}

var uuid;
var token = "";
var _token_ = "";
var rsa = require('../utils/cryptojs-master/cryptojs.js');
var Encrypt = require('../utils/jsencrypt.min.js');
var encryptor = new Encrypt.JSEncrypt();

function requestLoading(url, params, message, methods, hideLoad, hideToast, CallbackTimes = 0) {
  if (!appJs) {
    appJs = getApp();
  }
  let newUrl = url;
  let newParams = params;
  return new Promise(function(resolve, reject) {
    appJs.getPrivatekey().then(res => {
      encryptor.setPrivateKey(res);
      var method = methods || "GET";
      params = params || {};
      params.MpContentType = 1;
      if (url.substring(0, 1) == '/') {
        url = url.substr(1);
      }
      // token存在直接使用,不存在为空
      if (appJs) {
        token = appJs.globalData.token;
        _token_ = token;
      }
      // url拼接token和__shop__
      let __shop__ = wx.getStorageSync('organizationid') || '';
      // uuid存在直接使用,否则存储本地使用新的
      uuid = wx.getStorageSync('uuid');
      if (!uuid) {
        uuid = guid();
        wx.setStorageSync('uuid', uuid);
      }
      if (url.lastIndexOf("?") == -1) {
        url = url + '?_token_=' + _token_ + '&__shop__=' + __shop__ + '&uuid=' + uuid
      } else {
        url = url + '&_token_=' + _token_ + '&__shop__=' + __shop__ + '&uuid=' + uuid
      }
      let SourceStr = '';
      let Timest = timest();
      if (method == "GET" || method =="get") {
        let sha256sign = "";
        let str = "";
        for (let i = 0; i < 2; i++) {
          params.timestamp = Timest;
          params = objKeySort(params);
          str = Object.keys(params).map(function(key) {
            return "".concat(key, "=").concat(params[key]);
          }).join('&');
          str = '_token_=' + _token_ + '&__shop__=' + __shop__ + '&uuid=' + uuid + '&' + str;
          console.log(str)
          sha256sign = encryptor.sign(str, rsa.Crypto.SHA256, "SHA256");
          if (sha256sign.substring(sha256sign.length - 2) == '==') {
            break
          }
          Timest = Timest + 1;
        }
        if (sha256sign.substring(sha256sign.length - 2) != '==') {
          wx.showModal({
            title: '提示',
            content: '签名错误',
            showCancel: false,
            mask: true
          })
          return
        }
        SourceStr = encodeURIComponent(str);
        sha256sign = encodeURIComponent(sha256sign);
        params.sign = sha256sign;
      }
      if ((method == "POST" || method == 'post') || (method == "PUT" || method =='put')) {
        let index = url.lastIndexOf("?");
        let str = url.substring(index + 1, url.length);
        let parameter = JSON.stringify(params);
        let sha256sign = "";
        for (let i = 0; i < 2; i++) {
          str = str + '&timestamp=' + Timest + parameter;
          sha256sign = encryptor.sign(str, rsa.Crypto.SHA256, "SHA256");
          if (sha256sign.substring(sha256sign.length - 2) == '==') {
            break
          }
          Timest = Timest + 1;
        }
        if (sha256sign.substring(sha256sign.length - 2) != '==') {
          wx.showModal({
            title: '提示',
            content: '签名错误',
            showCancel: false,
            mask: true
          })
          return
        }
        SourceStr = str;
        sha256sign = encodeURIComponent(sha256sign);
        url = url + '&timestamp=' + Timest + '&sign=' + sha256sign;
      }
      if (method == "DELETE" || method =="delete") {
        let index = url.lastIndexOf("?");
        let str = '';
        let sha256sign = "";
        for (let i = 0; i < 2; i++) {
          str = url.substring(index + 1, url.length) + '&timestamp=' + Timest + JSON.stringify(params);
          sha256sign = encryptor.sign(str, rsa.Crypto.SHA256, "SHA256");
          if (sha256sign.substring(sha256sign.length - 2) == '==') {
            break
          }
          Timest = Timest + 1;
        }
        if (sha256sign.substring(sha256sign.length - 2) != '==') {
          wx.showModal({
            title: '提示',
            content: '签名错误',
            showCancel: false,
            mask: true
          })
          return
        }
        SourceStr = encodeURIComponent(str);
        sha256sign = encodeURIComponent(sha256sign);
        url = url + '&timestamp=' + Timest + '&sign=' + sha256sign;
      }
      var Device = "device/" + uuid
      var appId = ''
      if (wx.canIUse('getAccountInfoSync')) {
        const accountInfo = wx.getAccountInfoSync();
        appId = accountInfo.miniProgram.appId;
        var MicroApp = accountInfo.miniProgram.version;
        if (MicroApp) {
          Device = Device + " microapp/" + MicroApp
        }
      }
      wx.request({
        url: baseURL.baseURL + url,
        data: params,
        header: {
          'Content-Type': 'application/json',
          'Abp.Tenantld': '2',
          'authorization': token,
          "Device": Device
        },
        method: method,
        success: function(res) {
          wx.hideLoading()
          //请求成功
          //判断状态码---errCode状态根据后端定义来判断
          if (res.statusCode == 200) {
            if (res.data.Result.Code == 0) {
              resolve(res);
            } else if (res.data.Result.Code == 401 && url != 'api/services/app/MemberSession/GetLoginState') {
              resolve(res);
            } else if (res.data.Result.Code == 404 || res.data.Result.Code == 101) {
              resolve(res);
              console.log('请求参数:' + params, '请求接口:' + url, '返回结果:' + res)
            } else {
              resolve(res);
              console.log('请求参数:' + params, '请求接口:' + url, '返回结果:' + res)
            }
          } else if (res.statusCode == 401) {
            wx.reLaunch({
              url: '/pages/member/loginAndRegister/loginAndRegister',
            })
          } else if (res.statusCode == 402) {
            wx.showModal({
              title: '提示',
              content: res.data.Error.Message,
              showCancel: false,
              mask: true
            })
            reject(res);
          } else {
            if (res.data.Error.Code == 503) {
              if (CallbackTimes < 3) {
                CallbackTimes++;
                requestLoading(newUrl, newParams, message, methods, hideLoad, hideToast, CallbackTimes);
              }
            }
            wx.hideToast();
                setTimeout(function() {
                  wx.showToast({
                    title: '请求异常',
                    icon: 'none',
                    mask: true
                  })
                }, 500)
            console.log('请求参数:' + params, '请求接口:' + url, '返回结果:' + res)
            //其他异常
            reject('运行时错误,请稍后再试');
          }
        },
        fail: function(res) {
          console.log('请求参数:' + params, '请求接口:' + url, '返回结果:' + res)
          //请求失败
          reject(res);
        },
        complete: function(res) {},
      })
    })
  })
}
module.exports = {
  requestLoading: requestLoading
}
这是对接口发送请求的封装函数,返回的是一个Promise。在入参的时候CallbackTimes 是初始次数0,用来计算当前接口在并发的情况下执行了几次。在接口请求成功后如果返回的状态码是503即视为并发(不同公司对状态码的定义不同)。在对参数加密验签时,因为请求方法的不同进行了分别的处理,GET方法还对参数进行了排序。同时对验签后的字符串判断其结尾是不是==,不是的情况下对时间戳进行加一再次重试。当重试次数达到3次时,会中断操作提示验签异常。因为在特定的时间戳下生成的验签,我们的服务器是通不过的。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在android中采用java的填充算法 可以和ios .net跨平台使用 Java是一种广泛使用的面向对象的编程语言,由Sun Microsystems公司于1995年5月正式发布。它的设计目标是“一次编写,到处运行(Write Once, Run Anywhere)”,这意味着开发者可以使用Java编写应用程序,并在支持Java的任何平台上无需重新编译即可运行,这得益于其独特的跨平台性,通过Java虚拟机(JVM)实现不同操作系统上的兼容。 Java的特点包括: 面向对象:Java全面支持面向对象的特性,如封装、继承和多态,使得代码更易于维护和扩展。 安全:Java提供了丰富的安全特性,如禁止指针运算、自动内存管理和异常处理机制,以减少程序错误和恶意攻击的可能性。 可移植性:Java字节码可以在所有安装了JVM的设备上执行,从服务器到嵌入式系统,再到移动设备和桌面应用。 健壮性与高性能:Java通过垃圾回收机制确保内存的有效管理,同时也能通过JIT编译器优化来提升运行时性能。 标准库丰富:Java拥有庞大的类库,如Java SE(Java Standard Edition)包含基础API,用于开发通用应用程序;Java EE(Java Enterprise Edition)提供企业级服务,如Web服务、EJB等;而Java ME(Java Micro Edition)则针对小型设备和嵌入式系统。 社区活跃:Java有着全球范围内庞大的开发者社区和开源项目,持续推动技术进步和创新。 多线程支持:Java内建对多线程编程的支持,使并发编程变得更加简单直接。 动态性:Java可以通过反射、注解等机制实现在运行时动态加载类和修改行为,增加了程序的灵活性。 综上所述,Java凭借其强大的特性和广泛的适用范围,在企业级应用、互联网服务、移动开发等领域均扮演着举足轻重的角色,是现代软件开发不可或缺的重要工具之一。
对于使用C语言实现AES加密算法和RSA加密算法,你可以按照以下步骤进行操作: 1. AES加密算法的实现: - 首先,你需要导入相关的库文件,如openssl/aes.h。 - 接下来,你可以选择使用AES-128、AES-192或AES-256等不同的密钥长度。选择一个适合的密钥长度后,你可以生成一个随机的密钥,并设置初始向量(IV)。 - 然后,你可以使用AES加密函数,如AES_set_encrypt_key()和AES_encrypt(),将明文加密为密文。 - 最后,记得释放相关的资源,并清理内存。 2. RSA加密算法的实现: - 首先,你需要导入相关的库文件,如openssl/rsa.h。 - 接下来,你可以使用RSA_generate_key函数生成RSA的公钥和私钥。 - 然后,你可以使用RSA公钥对明文进行加密,使用RSA私钥对密文进行解密。 - 除此之外,你还可以使用RSA私钥对明文进行签名,使用RSA公钥对签名进行验证。 下面是一个示例代码,演示了如何使用C语言实现AES和RSA加密算法: ```c #include <stdio.h> #include <openssl/aes.h> #include <openssl/rsa.h> #include <openssl/pem.h> #include <openssl/err.h> // AES加密函数 void aes_encrypt(const unsigned char *plain_text, unsigned char *cipher_text, const unsigned char *key, const unsigned char *iv) { AES_KEY aes_key; AES_set_encrypt_key(key, 128, &aes_key); AES_cbc_encrypt(plain_text, cipher_text, 128, &aes_key, iv, AES_ENCRYPT); } // RSA加密函数 int rsa_encrypt(const unsigned char *plain_text, int plain_text_len, unsigned char *cipher_text, RSA *rsa) { int rsa_len = RSA_size(rsa); int result = RSA_public_encrypt(plain_text_len, plain_text, cipher_text, rsa, RSA_PKCS1_PADDING); return result; } int main() { // AES加密示例 unsigned char aes_key[16]; // 128位密钥 unsigned char iv[16]; // 初始向量 unsigned char plain_text[] = "Hello, AES!"; unsigned char cipher_text[128]; // 生成随机的密钥和初始向量 // ... // 使用AES加密算法加密明文 aes_encrypt(plain_text, cipher_text, aes_key, iv); printf("AES Encrypted Text: %s\n", cipher_text); // RSA加密示例 RSA *rsa = RSA_generate_key(2048, RSA_F4, NULL, NULL); unsigned char rsa_plain_text[] = "Hello, RSA!"; unsigned char rsa_cipher_text[256]; // 使用RSA公钥加密明文 rsa_encrypt(rsa_plain_text, sizeof(rsa_plain_text), rsa_cipher_text, rsa); printf("RSA Encrypted Text: %s\n", rsa_cipher_text); RSA_free(rsa); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值