【Flask】前端RSA加密后端Python解密示例

前言

在使用 Flask 开发用户登录API的时候,我之前都是明文传输 username 和 password。这种传输方式有一定的安全隐患,password 可能会在传输过程中被窃听而造成用户密码的泄漏。

那么我认为解决该问题的方法是这样的:在前端页面对数据进行加密,然后再发送到后端进行处理。

这一篇文章是前端用 RSA 的 publicKey 进行加密,然后后端用 Python 进行解密的示例。

工具列表

  • 后端:Python3
    • Flask
    • PyCrypto(PyCrytodome)
  • 前端

后端使用Cryptodome库进行密钥的生成和解密,前端则使用jsencrypt.js库进行加密。

阅读提醒

本文主要是提供前端RSA加密后端Python解密代码示例,不会做太详细的说明,也不会有代码打包下载链接,原理与步骤请细读示例代码或查阅相关资料。

后端

下面首先说明Python后端所用到的工具。

PyCrypto和PyCrytodome

PyCrypto 可能是 Python 中密码学方面最有名的第三方软件包。可惜的是,它的开发工作于2012年就已停止。幸运的是,有一个该项目的分支取代了 PyCrypto ,那就是 PyCrytodome

Linux下安装命令:

pip install pycryptodome 

Windows下安装命令:

pip install pycryptodomex 

PyCrytodome使用示例

安装好 pycryptodome 后,下面放上示例代码RSA_demo.py

#!/usr/bin/env python3
# coding=utf-8
# Author: yannanxiu
"""
create_rsa_key() - 创建RSA密钥
my_encrypt_and_decrypt() - 加密解密测试
"""

from Cryptodome.PublicKey import RSA
from Cryptodome.Cipher import PKCS1_OAEP, PKCS1_v1_5


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("my_private_rsa_key.bin", "wb") as f:
        f.write(encrypted_key)
    with open("my_rsa_public.pem", "wb") as f:
        f.write(key.publickey().exportKey())


def encrypt_and_decrypt_test(password="123456"):
    # 加载公钥
    recipient_key = RSA.import_key(
        open("my_rsa_public.pem").read()
    )
    cipher_rsa = PKCS1_v1_5.new(recipient_key)

    en_data = cipher_rsa.encrypt(b"123456")
    print(len(en_data), en_data)

    # 读取密钥
    private_key = RSA.import_key(
        open("my_private_rsa_key.bin").read(),
        passphrase=password
    )
    cipher_rsa = PKCS1_v1_5.new(private_key)
    data = cipher_rsa.decrypt(en_data, None)

    print(data)


if __name__ == '__main__':
    # create_rsa_key()
    encrypt_and_decrypt_test()

其中create_rsa_key()为创建密钥对,encrypt_and_decrypt_test()为加密解密的测试,用起来很简单对吧?

既然知道如何在Python端解密数据了,那么下面就是前端的代码:

前端

前端所用到的主要工具是jsencrypt.js

jsencrypt.js简介

jsencrypt.js是一个提供RSA加密、解密和密钥生成的JS库。其使用方式也非常简单。在其官网就有给出示例代码。

Flask工程示例

Python后端

新建一个Python脚本,取名为rsa_flask_demo.py,把下面代码复制过去。

#!/usr/bin/env python3
# coding=utf-8
# Author: yannanxiu

import os
from flask import Flask, render_template, request, current_app
from Cryptodome.PublicKey import RSA
from Cryptodome.Cipher import PKCS1_OAEP, PKCS1_v1_5
import base64
from urllib import parse

# 获取当前路径
curr_dir = os.path.dirname(os.path.realpath(__file__))
private_key_file = os.path.join(curr_dir, "my_private_rsa_key.bin")
public_key_file = os.path.join(curr_dir, "my_rsa_public.pem")

app = Flask(__name__)


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

    # base64decode
    data = base64.b64decode(data)

    private_key = RSA.import_key(
        open(curr_dir + "/my_private_rsa_key.bin").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


@app.route('/', methods=["GET", "POST"])
def rsa():
    public_key = None
    if "GET" == request.method:
        with open(public_key_file) as file:
            public_key = file.read()
    elif "POST" == request.method:
        username = request.values.get("username")
        password = request.values.get("passwd")
        current_app.logger.debug("username:" + username + "\n" + "password:" + password)

        # decrypt
        username_ret = decrypt_data(username)
        password_ret = decrypt_data(password)
        if username_ret and password_ret:
            current_app.logger.debug(username_ret.decode() + " " + password_ret.decode())

    return render_template("rsa_view.html", public_key=public_key)


@app.route('/js_rsa_test', methods=["GET", "POST"])
def js_rsa_test():
    return render_template("js_rsa_test.html")


if __name__ == '__main__':
    app.run(debug=True)

rsa_flask_demo.py与前面的RSA_demo.py脚本放在一起,再用RSA_demo.py生成一组密钥对。或者把前面生成密钥对文件放在同一个目录下也可以。

前端代码

在同一目录下新建一个templates文件夹,用来存放Flask的前端模板。

templates文件夹新建rsa_view.html,并拷贝下面代码过去,该HTML文件与Flask中的rsa()相对应。

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
    <script src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
    <script src="http://passport.cnblogs.com/scripts/jsencrypt.min.js"></script>

    <script type="text/javascript">
        // 使用jsencrypt类库加密js方法,
        function encryptRequest(reqUrl, data, publicKey) {
            var encrypt = new JSEncrypt();
            encrypt.setPublicKey(publicKey);
            // ajax请求发送的数据对象
            var sendData = new Object();
            // 将data数组赋给ajax对象
            for (var key in data) {
                sendData[key] = encrypt.encrypt(data[key]);
            }
            console.info(sendData);

            $.ajax({
                url: reqUrl,
                type: 'post',
                data: sendData,
                dataType: 'json',
                //contentType: 'application/json; charset=utf-8',
                success: function (data) {
                    console.info(data);
                },
                error: function (xhr) {
                    //console.error('出错了');
                }
            });
        }

        // Call this code when the page is done loading.
        $(function () {

            $('#testme').click(function () {

                var data = [];
                data['username'] = $('#username').val();
                data['passwd'] = $('#passwd').val();

                var pkey = $('#pubkey').val();
                encryptRequest('/', data, pkey);
            });
        });
    </script>
</head>

<body>
<form id="form1" runat="server">
    <div>
        <label for="pubkey">Public Key</label><br/>
        <textarea id="pubkey" rows="15" cols="65">
                {{ public_key }}
            </textarea><br/>
        <label for="input">Text to encrypt:</label><br/>
        name:<input id="username" name="username" type="text" value="user"></input><br/>
        password:<input id="passwd" name="passwd" type="password" value="123"></input><br/>
        <input id="testme" type="button" value="submit"/><br/>
    </div>
</form>
</body>
</html>

运行rsa_flask_demo.py,访问http://127.0.0.1:5000/,即可看到下面页面。直接点击submit按钮即可。

提交页面

当后台页面打印:

--------------------------------------------------------------------------------
DEBUG in rsa_flask_demo [F:/Flask/RSA_Flask/rsa_flask_demo.py:57]:
user 123
--------------------------------------------------------------------------------

即说明解密成功!

其他

rsa_flask_demo.py中的js_rsa_test()为JS脚本测试页面,其对应的HTML代码如下:

<!doctype html>
<html>
  <head>
    <title>JavaScript RSA Encryption</title>
    <script src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
    <script src="http://passport.cnblogs.com/scripts/jsencrypt.min.js"></script>
    <script type="text/javascript">

      // Call this code when the page is done loading.
      $(function() {

        // Run a quick encryption/decryption when they click.
        $('#testme').click(function() {

          // Encrypt with the public key...
          var encrypt = new JSEncrypt();
          encrypt.setPublicKey($('#pubkey').val());
          var encrypted = encrypt.encrypt($('#input').val());

          // Decrypt with the private key...
          var decrypt = new JSEncrypt();
          decrypt.setPrivateKey($('#privkey').val());
          var uncrypted = decrypt.decrypt(encrypted);

          // Now a simple check to see if the round-trip worked.
          if (uncrypted == $('#input').val()) {
            alert('It works!!!');
          }
          else {
            alert('Something went wrong....');
          }
        });
      });
    </script>
  </head>
  <body>
    <label for="privkey">Private Key</label><br/>
    <textarea id="privkey" rows="15" cols="65">-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQ
WMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNR
aY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4gwQco1KRMDSmXSMkDwIDAQAB
AoGAfY9LpnuWK5Bs50UVep5c93SJdUi82u7yMx4iHFMc/Z2hfenfYEzu+57fI4fv
xTQ//5DbzRR/XKb8ulNv6+CHyPF31xk7YOBfkGI8qjLoq06V+FyBfDSwL8KbLyeH
m7KUZnLNQbk8yGLzB3iYKkRHlmUanQGaNMIJziWOkN+N9dECQQD0ONYRNZeuM8zd
8XJTSdcIX4a3gy3GGCJxOzv16XHxD03GW6UNLmfPwenKu+cdrQeaqEixrCejXdAF
z/7+BSMpAkEA8EaSOeP5Xr3ZrbiKzi6TGMwHMvC7HdJxaBJbVRfApFrE0/mPwmP5
rN7QwjrMY+0+AbXcm8mRQyQ1+IGEembsdwJBAN6az8Rv7QnD/YBvi52POIlRSSIM
V7SwWvSK4WSMnGb1ZBbhgdg57DXaspcwHsFV7hByQ5BvMtIduHcT14ECfcECQATe
aTgjFnqE/lQ22Rk0eGaYO80cc643BXVGafNfd9fcvwBMnk0iGX0XRsOozVt5Azil
psLBYuApa66NcVHJpCECQQDTjI2AQhFc1yRnCU/YgDnSpJVm1nASoRUnU8Jfm3Oz
uku7JUXcVpt08DFSceCEX9unCuMcT72rAQlLpdZir876
-----END RSA PRIVATE KEY-----</textarea><br/>
    <label for="pubkey">Public Key</label><br/>
    <textarea id="pubkey" rows="15" cols="65">-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlOJu6TyygqxfWT7eLtGDwajtN
FOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76
xFxdU6jE0NQ+Z+zEdhUTooNRaY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4
gwQco1KRMDSmXSMkDwIDAQAB
-----END PUBLIC KEY-----</textarea><br/>
    <label for="input">Text to encrypt:</label><br/>
    <textarea id="input" name="input" type="text" rows=4 cols=70>This is a test!</textarea><br/>
    <input id="testme" type="button" value="Test Me!!!" /><br/>
  </body>
</html>

启动服务后访问http://127.0.0.1:5000/js_rsa_test即可。代码内容主要是先用jsencrypt.js加密数据,再进行解密,如果成功则弹出It works!!!对话框。不传输数据到后台,仅仅作为前端测试。

目录结构

│  my_private_rsa_key.bin
│  my_rsa_public.pem
│  RSA_demo.py
│  rsa_flask_demo.py
│
└─templates
        js_rsa_test.html
        rsa_view.html

猜你喜欢

转载自blog.csdn.net/u012163234/article/details/76436032