RSA算法【签名实战】【接口参数防止篡改】


前言

现在网络攻击,网络爬虫,黑客攻击无处不在,一些重要的API接口如果不加以保护,数据很容易被窃取,甚至给企业带来重大经济损失,那该如何防范呢?其实应对这种情况,行业给出了一个方案就是使用签名,接下来我就浅谈一下我的实现方式

一.准备条件

 1.安装flask
 2.安装python对应的rsa算法库Cryptodome
 3.sha256.js
 4.jsencrypt.js

二.签名思路

后端使用flask微型web框架提供服务,使用RSA算法生成公钥、私钥,将公钥返回给前端,前端将入参按照字典升序排序组装成url字符串,最后附上当前一个10位数字的系统时间戳参数,然后调用sh256.js加密得到一个签名值,使用ajax异步提交技术将入参和sign参数、当前一个10位数字的系统时间戳参数使用rsa加密,然后再将数据发送给后端,后端拿着RSA加密算法的私钥对时间戳参数进行解密,然后对签名参数进行校验,签名校验通过则进行下一步的业务处理,否则返回一个错误提示给前端。


三.编码实现

3.1首先将python需要的第三方库都安装起来

安装flask框架可以参考 w3cshool文档
https://www.w3cschool.cn/flask/flask_environment.html

3.2 安装python实现RSA算法库

pip install pycryptodomex 

3.3生成RSA公钥和私钥证书

# 导入Falsk
from Cryptodome.PublicKey import RSA
from Cryptodome.Cipher import  PKCS1_v1_5
from flask import Flask, render_template, session,request
from urllib import parse
app = Flask(__name__, static_folder='./static', template_folder='./templates')  # 配置资源路径

def create_rsa_key(password="123456"):
    """
    创建RSA密钥
    步骤说明:
    1、从 Crypto.PublicKey 包中导入 RSA,创建一个密码
    2、生成 1024/2048 位的 RSA 密钥
    3、调用 RSA 密钥实例的 exportKey 方法,传入密码、使用的 PKCS 标准以及加密方案这三个参数。
    4、将私钥写入磁盘的文件。
    5、使用方法链调用 publickey 和 exportKey 方法生成公钥,写入磁盘上的文件。
    """
    key = RSA.generate(1024)
    encrypted_key = key.exportKey(passphrase=password, pkcs=8,
                                  protection="scryptAndAES128-CBC")
    with open("./cert/rsa_private_key.pem", "wb") as f:
        f.write(encrypted_key)
    with open("./cert/rsa_public_key.pem", "wb") as f:
        f.write(key.publickey().exportKey())
    return

3.4 编写html,发送网络请求

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<input type="hidden" id="rsa_pubkey" value="{{rsa_pubkey}}">
<div>
    <input type="text" id="name" placeholder="请输入您的姓名"  maxlength="32"/>
</div>
<div>
    <input type="text" id="age" placeholder="请输入您的年龄" maxlength="10"/>
</div>
<div>
    <input type="text" id="salary" placeholder="请输入您的薪水" maxlength="10"/>
</div>
<div>
    <button onclick="submit()">提交</button>
</div>
</body>
</html>
<script type='text/javascript' src='https://cdn.bootcdn.net/ajax/libs/jsencrypt/3.1.0/jsencrypt.js'></script>
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<script>
   function submit() {
       var data = {};
       data['name'] = $('#name').val();
       data['age']= $('#age').val();
       data['salary'] = $('#salary').val();
       var result = sort_object(data)
       //获取签名参数
       var salt = String(getTime());
       var sign = getSign(result,salt);
       data['sign'] = sign;
       data['salt'] = encrypt(salt);

        $.ajax({
         url:'/submit',
         type:'post',
         data:data,
         success:function(result) {
           console.log(result);
         },
         error:function(err) {
           console.log(err);
         }

        });

   }


 //安装字典排序,获取前签名参数
   function sort_object(dict) {
       var key_list = []

       for(var item in dict){
           key_list.push(item)
       }
       key_list.sort()
       var result = ''
       for (var i in key_list) {
            var key_name = key_list[i]
             result+=(key_name+'='+dict[key_name]+'&');
       }

       return result.substring(0,result.length-1)
   }
  //sha256加密方法
   function SHA256(s) {
       var chrsz = 8;
       var hexcase = 0;

       function safe_add(x, y) {
           var lsw = (x & 0xFFFF) + (y & 0xFFFF);
           var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
           return (msw << 16) | (lsw & 0xFFFF);
       }

       function S(X, n) {
           return (X >>> n) | (X << (32 - n));
       }

       function R(X, n) {
           return (X >>> n);
       }

       function Ch(x, y, z) {
           return ((x & y) ^ ((~x) & z));
       }

       function Maj(x, y, z) {
           return ((x & y) ^ (x & z) ^ (y & z));
       }

       function Sigma0256(x) {
           return (S(x, 2) ^ S(x, 13) ^ S(x, 22));
       }

       function Sigma1256(x) {
           return (S(x, 6) ^ S(x, 11) ^ S(x, 25));
       }

       function Gamma0256(x) {
           return (S(x, 7) ^ S(x, 18) ^ R(x, 3));
       }

       function Gamma1256(x) {
           return (S(x, 17) ^ S(x, 19) ^ R(x, 10));
       }

       function core_sha256(m, l) {
           var K = new Array(0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0xFC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x6CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2);
           var HASH = new Array(0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19);
           var W = new Array(64);
           var a, b, c, d, e, f, g, h, i, j;
           var T1, T2;
           m[l >> 5] |= 0x80 << (24 - l % 32);
           m[((l + 64 >> 9) << 4) + 15] = l;
           for (var i = 0; i < m.length; i += 16) {
               a = HASH[0];
               b = HASH[1];
               c = HASH[2];
               d = HASH[3];
               e = HASH[4];
               f = HASH[5];
               g = HASH[6];
               h = HASH[7];
               for (var j = 0; j < 64; j++) {
                   if (j < 16) W[j] = m[j + i];
                   else W[j] = safe_add(safe_add(safe_add(Gamma1256(W[j - 2]), W[j - 7]), Gamma0256(W[j - 15])), W[j - 16]);
                   T1 = safe_add(safe_add(safe_add(safe_add(h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j]);
                   T2 = safe_add(Sigma0256(a), Maj(a, b, c));
                   h = g;
                   g = f;
                   f = e;
                   e = safe_add(d, T1);
                   d = c;
                   c = b;
                   b = a;
                   a = safe_add(T1, T2);
               }
               HASH[0] = safe_add(a, HASH[0]);
               HASH[1] = safe_add(b, HASH[1]);
               HASH[2] = safe_add(c, HASH[2]);
               HASH[3] = safe_add(d, HASH[3]);
               HASH[4] = safe_add(e, HASH[4]);
               HASH[5] = safe_add(f, HASH[5]);
               HASH[6] = safe_add(g, HASH[6]);
               HASH[7] = safe_add(h, HASH[7]);
           }
           return HASH;
       }

       function str2binb(str) {
           var bin = Array();
           var mask = (1 << chrsz) - 1;
           for (var i = 0; i < str.length * chrsz; i += chrsz) {
               bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i % 32);
           }
           return bin;
       }

       function Utf8Encode(string) {
           string = string.replace(/\r\n/g, "\n");
           var utftext = "";
           for (var n = 0; n < string.length; n++) {
               var c = string.charCodeAt(n);
               if (c < 128) {
                   utftext += String.fromCharCode(c);
               } else if ((c > 127) && (c < 2048)) {
                   utftext += String.fromCharCode((c >> 6) | 192);
                   utftext += String.fromCharCode((c & 63) | 128);
               } else {
                   utftext += String.fromCharCode((c >> 12) | 224);
                   utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                   utftext += String.fromCharCode((c & 63) | 128);
               }
           }
           return utftext;
       }

       function binb2hex(binarray) {
           var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
           var str = "";
           for (var i = 0; i < binarray.length * 4; i++) {
               str += hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF) +
                   hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8)) & 0xF);
           }
           return str;
       }

       s = Utf8Encode(s);
       return binb2hex(core_sha256(str2binb(s), s.length * chrsz));
   }



   //获取13位数字时间戳
   //1618 716 106 087
   function getTime() {

       return new Date().getTime();
   }


   //rsa公钥加密
   function encrypt(text) {
       var rsa_pubkey = $('#rsa_pubkey').val();
       let encryptor = new JSEncrypt()
       encryptor.setPublicKey(rsa_pubkey)
       return encryptor.encrypt(text)
   }
   //生成签名参数
   function getSign(result,salt) {
    result+='&key='+salt;
    return SHA256(result)
   }
</script>

3.5 编写python签名方法

引入日志模块

  import logging

设置日志输出到文件

logger = logging.getLogger()
logger.setLevel(logging.INFO)  # Log等级总开关
# 第二步,创建一个handler,用于写入日志文件
rq = time.strftime('%Y%m%d%H%M', time.localtime(time.time()))
log_path = os.path.dirname('./Logs/')
log_name = log_path + rq + '.log'
logfile = log_name
fh = logging.FileHandler(logfile, mode='w')
fh.setLevel(logging.DEBUG)  # 输出到file的log等级的开关
# 第三步,定义handler的输出格式
formatter = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")
fh.setFormatter(formatter)
# 第四步,将logger添加到handler里面
logger.addHandler(fh)

编写数据处理签名的方法

@app.route('/submit', methods=['POST'])
def submit():
    try:
        resultForm = dict(request.form)
        logger.info('submit|request|success|params=%s' % str(resultForm))
        # 取出盐值
        salt = resultForm.pop('salt')
        originSalt = decrypt_data(salt)  # rsa私钥解密
        if not originSalt:
            logger.error('submit|request|error|decrypt salt failed')
            return apiJson(1, '解密失败')
        # 将字符串转换为数字
        originSalt = int(originSalt)
        nowSencods = getSeconds()
        if nowSencods - originSalt > 1 * 60 * 60:
            logger.error('submit|request|error|url time out')
            return apiJson(1, 'url已经失效')
        sign = resultForm.pop('sign')
        urlParams = DictionAiry(resultForm)
        urlParams = '{0}{1}'.format(urlParams, '&key=' + str(originSalt))
        # 生成签名
        Newsign = hashlib.sha256(urlParams.encode('utf-8')).hexdigest()
        if sign != Newsign:
            logger.error('submit|request|error|sign|sign=%s,newSign=%s' % (sign, Newsign))
            return apiJson(1, '数据被篡改,签名校验失败')
        # 校验名称
        name = resultForm['name']
        if not name:
            logger.error('submit|request|error|name is empty')
            return apiJson(1, '请输入名称')
        if len(name) > 64:
            logger.error('submit|request|error|name is over 64 bytes')
            return apiJson(1, '名称长度非法')
        age = resultForm['age']
        # 匹配1-100
        if not re.match(r'[1-9]\d?$|0$|100$', age):
            logger.error('submit|request|error| age illegal')
            return apiJson(1, '年龄非法')
        # 校验薪水
        salary = resultForm['salary']
        if not salary.isnumeric() or float(salary) <= 0 or float(salary) > 100000:
            logger.error('submit|request|error| salary illegal')
            return apiJson(1, '薪水非法')
        return apiJson(0, '操作成功', resultForm)
    except BaseException as e:
        logger.error('submit|request|error| system inner error=%s'%(e))
        return apiJson(500,'系统内部错误')

整个控制器文件

# 导入Falsk
import datetime
import hashlib
import os
import re
import time
import base64
import logging
from Cryptodome.PublicKey import RSA
from Cryptodome.Cipher import  PKCS1_v1_5
from flask import Flask, render_template, session,request
from urllib import parse


app = Flask(__name__, static_folder='./static', template_folder='./templates')  # 配置资源路径
rsa_public_file = './cert/rsa_public_key.pem'
rsa_private_file = './cert/rsa_private_key.pem'
# 设置session Key
app.config['SECRET_KEY'] = os.urandom(24)

logger = logging.getLogger()
logger.setLevel(logging.INFO)  # Log等级总开关
# 第二步,创建一个handler,用于写入日志文件
rq = time.strftime('%Y%m%d%H%M', time.localtime(time.time()))
log_path = os.path.dirname('./Logs/')
log_name = log_path + rq + '.log'
logfile = log_name
fh = logging.FileHandler(logfile, mode='w')
fh.setLevel(logging.DEBUG)  # 输出到file的log等级的开关
# 第三步,定义handler的输出格式
formatter = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")
fh.setFormatter(formatter)
# 第四步,将logger添加到handler里面
logger.addHandler(fh)



@app.route('/')
def index():
     rsa_pubkey = getCertContent(rsa_public_file)
     rsa_privkey = getCertContent(rsa_private_file)
     session['rsa_pubkey'] = rsa_pubkey
     session['rsa_privkey'] = rsa_privkey
     return render_template('demo/index.html',rsa_pubkey=rsa_pubkey)


@app.route('/submit', methods=['POST'])
def submit():
    try:
        resultForm = dict(request.form)
        logger.info('submit|request|success|params=%s' % str(resultForm))
        # 取出盐值
        salt = resultForm.pop('salt')
        originSalt = decrypt_data(salt)  # rsa私钥解密
        if not originSalt:
            logger.error('submit|request|error|decrypt salt failed')
            return apiJson(1, '解密失败')
        # 将字符串转换为数字
        originSalt = int(originSalt)
        nowSencods = getSeconds()
        if nowSencods - originSalt > 1 * 60 * 60:
            logger.error('submit|request|error|url time out')
            return apiJson(1, 'url已经失效')
        sign = resultForm.pop('sign')
        urlParams = DictionAiry(resultForm)
        urlParams = '{0}{1}'.format(urlParams, '&key=' + str(originSalt))
        # 生成签名
        Newsign = hashlib.sha256(urlParams.encode('utf-8')).hexdigest()
        if sign != Newsign:
            logger.error('submit|request|error|sign|sign=%s,newSign=%s' % (sign, Newsign))
            return apiJson(1, '数据被篡改,签名校验失败')
        # 校验名称
        name = resultForm['name']
        if not name:
            logger.error('submit|request|error|name is empty')
            return apiJson(1, '请输入名称')
        if len(name) > 64:
            logger.error('submit|request|error|name is over 64 bytes')
            return apiJson(1, '名称长度非法')
        age = resultForm['age']
        # 匹配1-100
        if not re.match(r'[1-9]\d?$|0$|100$', age):
            logger.error('submit|request|error| age illegal')
            return apiJson(1, '年龄非法')
        # 校验薪水
        salary = resultForm['salary']
        if not salary.isnumeric() or float(salary) <= 0 or float(salary) > 100000:
            logger.error('submit|request|error| salary illegal')
            return apiJson(1, '薪水非法')
        return apiJson(0, '操作成功', resultForm)
    except BaseException as e:
        logger.error('submit|request|error| system inner error=%s'%(e))
        return apiJson(500,'系统内部错误')
def apiJson(code=0,msg='操作成功',data=[]):
    return {
        'code':code,
        'msg':msg,
        'data':data
    }
def create_rsa_key(password="123456"):
    """
    创建RSA密钥
    步骤说明:
    1、从 Crypto.PublicKey 包中导入 RSA,创建一个密码
    2、生成 1024/2048 位的 RSA 密钥
    3、调用 RSA 密钥实例的 exportKey 方法,传入密码、使用的 PKCS 标准以及加密方案这三个参数。
    4、将私钥写入磁盘的文件。
    5、使用方法链调用 publickey 和 exportKey 方法生成公钥,写入磁盘上的文件。
    """
    key = RSA.generate(1024)
    encrypted_key = key.exportKey(passphrase=password, pkcs=8,
                                  protection="scryptAndAES128-CBC")
    with open("./cert/rsa_private_key.pem", "wb") as f:
        f.write(encrypted_key)
    with open("./cert/rsa_public_key.pem", "wb") as f:
        f.write(key.publickey().exportKey())
    return
def DictionAiry(params):
   print(params)
   if type(params) == type({'age':18}):
        dict = {}
        for i in sorted(params):
            dict[i]=params[i]
        result = ''
        for item in dict:
            tmp='%s=%s&'%(item,dict[item])
            result+=tmp
        return result.strip('&')
   else:
       print('数据类型非字典,传入参数错误')
       return

def decrypt_data(inputdata, code="123456"):
    # URLDecode
    data = parse.unquote(inputdata)

    # base64decode
    data = base64.b64decode(data)

    private_key = RSA.import_key(
        open('./cert/rsa_private_key.pem').read(),
        passphrase=code
    )
    # 使用 PKCS1_v1_5,不要用 PKCS1_OAEP
    # 使用 PKCS1_OAEP 的话,前端 jsencrypt.js 加密的数据解密不了
    cipher_rsa = PKCS1_v1_5.new(private_key)

    # 当解密失败,会返回 sentinel
    sentinel = None
    ret = cipher_rsa.decrypt(data, sentinel)

    return ret

def getCertContent(filePath):
    with open(filePath,'r') as reader:
        return reader.read()

#获取13位时间戳
def getSeconds():
    datetime_object = datetime.datetime.now()
    now_timetuple = datetime_object.timetuple()
    now_second = time.mktime(now_timetuple)
    mow_millisecond = int(now_second * 1000 + datetime_object.microsecond / 1000)
    return  mow_millisecond
if __name__ == '__main__':
    # flask 应用提供了一个简易服务器,用于测试
    app.run(host='127.0.0.1', port=5000, debug=True)

总结

记录接口安全设计的一种思路
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值